From 991df6ad0a703d97e1e192a0a6106e1d9ba47e8e Mon Sep 17 00:00:00 2001 From: Anne Philipp <anne.philipp@univie.ac.at> Date: Tue, 15 May 2018 00:11:29 +0200 Subject: [PATCH] finished documentation (except plot_retrieved) --- python/Control.py | 259 ------------------- python/Disagg.py | 85 +++--- python/ECFlexpart.py | 377 ++++++++++++++------------- python/ECMWF_ENV | 4 - python/GribTools.py | 70 ++--- python/MARSretrieval.py | 43 +++- python/Tools.py | 118 ++++++--- python/UIOFiles.py | 121 +++++---- python/compilejob.ksh | 6 +- python/getMARSdata.py | 282 +++++++++++--------- python/install.py | 175 +++++++------ python/job.ksh | 4 +- python/job.temp | 2 +- python/joboper.ksh | 4 +- python/plot_retrieved.py | 498 ++++++++++++++++++++++++------------ python/prepareFLEXPART.py | 159 +++++++----- python/profiling.py | 37 +-- python/submit.py | 81 +++--- python/testsuite.py | 106 +++++--- src/Makefile.local.gfortran | 4 +- src/Makefile.local.ifort | 16 +- 21 files changed, 1298 insertions(+), 1153 deletions(-) delete mode 100644 python/Control.py delete mode 100644 python/ECMWF_ENV diff --git a/python/Control.py b/python/Control.py deleted file mode 100644 index 8fb6a45..0000000 --- a/python/Control.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -#************************************************************************ -# TODO AP -# - write a test class -# - check documentation -#************************************************************************ -""" -@Author: Leopold Haimberger (University of Vienna) - -@Date: November 2015 - -@ChangeHistory: - February 2018 - Anne Philipp (University of Vienna): - - applied PEP8 style guide - - added documentation - - applied some minor modifications in programming style/structure - - outsource of class Control - -@License: - (C) Copyright 2015-2018. - - 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. - -@Requirements: - A standard python 2.6 or 2.7 installation - -@Description: - The Control files are the steering part of the FLEXPART extraction - software. All necessary parameters needed to retrieve the data fields - from the MARS archive for driving FLEXPART are set in a Control file. - Some specific parameters like the start and end dates can be overwritten - by the command line parameters, but in generel all parameters needed - for a complete set of fields for FLEXPART can be set in the Control files. - -""" -# ------------------------------------------------------------------------------ -# MODULES -# ------------------------------------------------------------------------------ -import os -import inspect -import Tools -# ------------------------------------------------------------------------------ -# CLASS -# ------------------------------------------------------------------------------ -class Control: - ''' - Class containing the information of the ECMWFDATA control file. - - Contains all the parameters of control files, which are e.g.: - DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, - STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, - LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, - OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, SMOOTH, FORMAT, - ADDPAR, WRF, CWC, PREFIX, ECSTORAGE, ECTRANS, ECFSDIR, - MAILOPS, MAILFAIL, GRIB2FLEXPART, FLEXPARTDIR, - BASETIME, DATE_CHUNK, DEBUG, INPUTDIR, OUTPUTDIR, FLEXPART_ROOT_SCRIPTS - - For more information about format and content of the parameter - see documentation. - - ''' - - def __init__(self, filename): - ''' - @Description: - Initialises the instance of Control class and defines and - assign all controlfile variables. Set default values if - parameter was not in CONTROL file. - - @Input: - self: instance of Control class - Description see class documentation. - - filename: string - Name of control file. - - @Return: - <nothing> - ''' - - # read whole CONTROL file - with open(filename) as f: - fdata = f.read().split('\n') - - # go through every line and store parameter - # as class variable - for ldata in fdata: - data = ldata.split() - if len(data) > 1: - if 'm_' in data[0].lower(): - data[0] = data[0][2:] - if data[0].lower() == 'class': - data[0] = 'marsclass' - if data[0].lower() == 'day1': - data[0] = 'start_date' - if data[0].lower() == 'day2': - data[0] = 'end_date' - if data[0].lower() == 'addpar': - if '/' in data[1]: - # remove leading '/' sign from addpar content - if data[1][0] == '/': - data[1] = data[1][1:] - dd = data[1].split('/') - data = [data[0]] - for d in dd: - data.append(d) - pass - if len(data) == 2: - if '$' in data[1]: - setattr(self, data[0].lower(), data[1]) - while '$' in data[1]: - i = data[1].index('$') - j = data[1].find('{') - k = data[1].find('}') - var = os.getenv(data[1][j+1:k]) - if var is not None: - data[1] = data[1][:i] + var + data[1][k+1:] - else: - Tools.myerror(None, - 'Could not find variable ' + - data[1][j+1:k] + - ' while reading ' + - filename) - setattr(self, data[0].lower() + '_expanded', data[1]) - else: - if data[1].lower() != 'none': - setattr(self, data[0].lower(), data[1]) - else: - setattr(self, data[0].lower(), None) - elif len(data) > 2: - setattr(self, data[0].lower(), (data[1:])) - else: - pass - - # check a couple of necessary attributes if they contain values - # otherwise set default values - if not hasattr(self, 'start_date'): - self.start_date = None - if not hasattr(self, 'end_date'): - self.end_date = self.start_date - if not hasattr(self, 'accuracy'): - self.accuracy = 24 - if not hasattr(self, 'omega'): - self.omega = '0' - if not hasattr(self, 'cwc'): - self.cwc = '0' - if not hasattr(self, 'omegadiff'): - self.omegadiff = '0' - if not hasattr(self, 'etadiff'): - self.etadiff = '0' - if not hasattr(self, 'levelist'): - if not hasattr(self, 'level'): - print('Warning: neither levelist nor level \ - specified in CONTROL file') - else: - self.levelist = '1/to/' + self.level - else: - if 'to' in self.levelist: - self.level = self.levelist.split('/')[2] - else: - self.level = self.levelist.split('/')[-1] - - if not hasattr(self, 'maxstep'): - # find out maximum step - self.maxstep = 0 - for s in self.step: - if int(s) > self.maxstep: - self.maxstep = int(s) - else: - self.maxstep = int(self.maxstep) - - if not hasattr(self, 'prefix'): - self.prefix = 'EN' - if not hasattr(self, 'makefile'): - self.makefile = None - if not hasattr(self, 'basetime'): - self.basetime = None - if not hasattr(self, 'date_chunk'): - self.date_chunk = '3' - if not hasattr(self, 'grib2flexpart'): - self.grib2flexpart = '0' - - # script directory - self.ecmwfdatadir = os.path.dirname(os.path.abspath(inspect.getfile( - inspect.currentframe()))) + '/../' - # Fortran source directory - self.exedir = self.ecmwfdatadir + 'src/' - - # FLEXPART directory - if not hasattr(self, 'flexpart_root_scripts'): - self.flexpart_root_scripts = self.ecmwfdatadir - - return - - def __str__(self): - ''' - @Description: - Prepares a single string with all the comma seperated Control - class attributes including their values. - - Example: - {'kids': 0, 'name': 'Dog', 'color': 'Spotted', - 'age': 10, 'legs': 2, 'smell': 'Alot'} - - @Input: - self: instance of Control class - Description see class documentation. - - @Return: - string of Control class attributes with their values - ''' - - attrs = vars(self) - - return ', '.join("%s: %s" % item for item in attrs.items()) - - def tolist(self): - ''' - @Description: - Just generates a list of strings containing the attributes and - assigned values except the attributes "_expanded", "exedir", - "ecmwfdatadir" and "flexpart_root_scripts". - - @Input: - self: instance of Control class - Description see class documentation. - - @Return: - l: list - A sorted list of the all Control class attributes with - their values except the attributes "_expanded", "exedir", - "ecmwfdatadir" and "flexpart_root_scripts". - ''' - - attrs = vars(self) - l = list() - - for item in attrs.items(): - if '_expanded' in item[0]: - pass - elif 'exedir' in item[0]: - pass - elif 'flexpart_root_scripts' in item[0]: - pass - elif 'ecmwfdatadir' in item[0]: - pass - else: - if type(item[1]) is list: - stot = '' - for s in item[1]: - stot += s + ' ' - - l.append("%s %s" % (item[0], stot)) - else: - l.append("%s %s" % item) - - return sorted(l) diff --git a/python/Disagg.py b/python/Disagg.py index cf2f4c9..20aee50 100644 --- a/python/Disagg.py +++ b/python/Disagg.py @@ -2,51 +2,50 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -# - make a class out of this ??? -# - write a test class +# - check alist of size 4 ? +# - write a test, IMPORTANT #************************************************************************ -""" -@Author: Anne Philipp (University of Vienna) +#******************************************************************************* +# @Author: Anne Philipp (University of Vienna) +# +# @Date: March 2018 +# +# @Change History: +# November 2015 - Leopold Haimberger (University of Vienna): +# - migration of the methods dapoly and darain from Fortran +# (flex_extract_v6 and earlier) to Python +# +# April 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added structured documentation +# - outsourced the disaggregation functions dapoly and darain +# to a new module named Disagg +# +# @License: +# (C) Copyright 2015-2018. +# +# 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. +# +# @Module Description: +# Disaggregation of deaccumulated flux data from an ECMWF model FG field. +# Initially the flux data to be concerned are: +# - large-scale precipitation +# - convective precipitation +# - surface sensible heat flux +# - surface solar radiation +# - u stress +# - v stress +# Different versions of disaggregation is provided for rainfall +# data (darain, modified linear) and the surface fluxes and +# stress data (dapoly, cubic polynomial). +# +# @Module Content: +# - dapoly +# - darain +# +#******************************************************************************* -@Date: March 2018 - -@ChangeHistory: - November 2015 - Leopold Haimberger (University of Vienna): - - integrated methods dapoly and darain from Fortran to Python - - April 2018 - Anne Philipp (University of Vienna): - - applied PEP8 style guide - - added structured documentation - - outsourced the disaggregation functions dapoly and darain - to a new module named Disagg - -@License: - (C) Copyright 2015-2018. - - 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. - -@Requirements: - A standard python 2.6 or 2.7 installation - -@Description: - Further documentation may be obtained from www.flexpart.eu. - - Functionality provided: - Disaggregation of deaccumulated flux data from an ECMWF model FG field. - Initially the flux data to be concerned are: - - large-scale precipitation - - convective precipitation - - surface sensible heat flux - - surface solar radiation - - u stress - - v stress - - Different versions of disaggregation is provided for rainfall - data (darain, modified linear) and the surface fluxes and - stress data (dapoly, cubic polynomial). - -""" # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ diff --git a/python/ECFlexpart.py b/python/ECFlexpart.py index 8e1e57f..0537feb 100644 --- a/python/ECFlexpart.py +++ b/python/ECFlexpart.py @@ -2,102 +2,112 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -#AP # - specifiy file header documentation +# - add class description in header information # - apply classtests # - add references to ECMWF specific software packages +# - add describtion of deacc_fluxes +# - change name of func deacc ( weil disagg ) +# - add desc of retrieve function #************************************************************************ -""" -@Author: Anne Fouilloux (University of Oslo) - -@Date: October 2014 - -@ChangeHistory: - November 2015 - Leopold Haimberger (University of Vienna): - - extended with Class Control - - removed functions mkdir_p, daterange, years_between, months_between - - added functions darain, dapoly, toparamId, init128, normalexit, - myerror, cleanup, install_args_and_control, - interpret_args_and_control, - - removed function __del__ in class EIFLexpart - - added the following functions in EIFlexpart: - - create_namelist - - process_output - - deacc_fluxes - - modified existing EIFlexpart - functions for the use in - flex_extract - - retrieve also longer term forecasts, not only analyses and - short term forecast data - - added conversion into GRIB2 - - added conversion into .fp format for faster execution of FLEXPART - (see https://www.flexpart.eu/wiki/FpCtbtoWo4FpFormat) - - February 2018 - Anne Philipp (University of Vienna): - - applied PEP8 style guide - - added documentation - - outsourced class Control - - outsourced class MarsRetrieval - - changed class name from EIFlexpart to ECFlexpart - - applied minor code changes (style) - -@License: - (C) Copyright 2014-2018. - - 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. - -@Requirements: - - A standard python 2.6 or 2.7 installation - - dateutils - - matplotlib (optional, for debugging) - - ECMWF specific packages, all available from https://software.ecmwf.int/ - ECMWF WebMARS, gribAPI with python enabled, emoslib and - ecaccess web toolkit - -@Description: - Further documentation may be obtained from www.flexpart.eu. - - Functionality provided: - Prepare input 3D-wind fields in hybrid coordinates + - surface fields for FLEXPART runs -""" +#******************************************************************************* +# @Author: Anne Fouilloux (University of Oslo) +# +# @Date: October 2014 +# +# @Change History: +# +# November 2015 - Leopold Haimberger (University of Vienna): +# - extended with class Control +# - removed functions mkdir_p, daterange, years_between, months_between +# - added functions darain, dapoly, toparamId, init128, normalexit, +# myerror, cleanup, install_args_and_control, +# interpret_args_and_control, +# - removed function __del__ in class EIFLexpart +# - added the following functions in EIFlexpart: +# - create_namelist +# - process_output +# - deacc_fluxes +# - modified existing EIFlexpart - functions for the use in +# flex_extract +# - retrieve also longer term forecasts, not only analyses and +# short term forecast data +# - added conversion into GRIB2 +# - added conversion into .fp format for faster execution of FLEXPART +# (see https://www.flexpart.eu/wiki/FpCtbtoWo4FpFormat) +# +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation +# - removed function getFlexpartTime in class ECFlexpart +# - outsourced class ControlFile +# - outsourced class MarsRetrieval +# - changed class name from EIFlexpart to ECFlexpart +# - applied minor code changes (style) +# - removed "dead code" , e.g. retrieval of Q since it is not needed +# - removed "times" parameter from retrieve-method since it is not used +# +# @License: +# (C) Copyright 2014-2018. +# +# 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. +# +# @Class Description: +# FLEXPART needs grib files in a specifc format. All necessary data fields +# for one time step are stored in a single file. The class represents an +# instance with all the parameter and settings necessary for retrieving +# MARS data and modifing them so they are fitting FLEXPART need. The class +# is able to disaggregate the fluxes and convert grid types to the one needed +# by FLEXPART, therefore using the FORTRAN program. +# +# @Class Content: +# - __init__ +# - write_namelist +# - retrieve +# - process_output +# - create +# - deacc_fluxes +# +#******************************************************************************* + # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ import subprocess -from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter -import traceback import shutil import os -import errno import sys import inspect import glob import datetime -from string import atoi from numpy import * +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter ecapi = True try: import ecmwfapi except ImportError: ecapi = False from gribapi import * + +# software specific classes and modules from flex_extract from GribTools import GribTools from Tools import init128, toparamId, silentremove, product -from Control import Control +from ControlFile import ControlFile from MARSretrieval import MARSretrieval import Disagg + # ------------------------------------------------------------------------------ # CLASS # ------------------------------------------------------------------------------ class ECFlexpart: ''' - Class to retrieve ECMWF data specific for running FLEXPART. + Class to retrieve FLEXPART specific ECMWF data. ''' # -------------------------------------------------------------------------- # CLASS FUNCTIONS # -------------------------------------------------------------------------- - def __init__(self, c, fluxes=False): #done/ verstehen + def __init__(self, c, fluxes=False): ''' @Description: Creates an object/instance of ECFlexpart with the @@ -107,8 +117,8 @@ class ECFlexpart: self: instance of ECFlexpart The current object of the class. - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL file, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -129,7 +139,7 @@ class ECFlexpart: <nothing> ''' - # different mars types for retrieving reanalysis data for flexpart + # different mars types for retrieving data for flexpart self.types = dict() try: if c.maxstep > len(c.type): # Pure forecast mode @@ -146,25 +156,16 @@ class ECFlexpart: self.inputdir = c.inputdir self.basetime = c.basetime self.dtime = c.dtime - #self.mars = {} i = 0 - #j = 0 if fluxes is True and c.maxstep < 24: # no forecast beyond one day is needed! # Thus, prepare flux data manually as usual - # with only FC fields with start times at 00/12 + # with only forecast fields with start times at 00/12 # (but without 00/12 fields since these are # the initialisation times of the flux fields # and therefore are zero all the time) - self.types['FC'] = {'times': '00/12', - 'steps': '{}/to/12/by/{}'.format(c.dtime, - c.dtime)} - #i = 1 - #for k in [0, 12]: - # for j in range(int(c.dtime), 13, int(c.dtime)): - # self.mars['{:0>3}'.format(i * int(c.dtime))] = \ - # [c.type[1], '{:0>2}'.format(k), '{:0>3}'.format(j)] - # i += 1 + self.types[c.type[1]] = {'times': '00/12', 'steps': + '{}/to/12/by/{}'.format(c.dtime, c.dtime)} else: for ty, st, ti in zip(c.type, c.step, c.time): btlist = range(24) @@ -188,14 +189,8 @@ class ECFlexpart: if len(self.types[ty]['steps']) > 0: self.types[ty]['steps'] += '/' self.types[ty]['steps'] += st - - #self.mars['{:0>3}'.format(j)] = [ty, - # '{:0>2}'.format(int(ti)), - # '{:0>3}'.format(int(st))] - #j += int(c.dtime) - i += 1 - print 'EC init: ', self.types #AP + # Different grids need different retrievals # SH = Spherical Harmonics, GG = Gaussian Grid, # OG = Output Grid, ML = MultiLevel, SL = SingleLevel @@ -273,7 +268,7 @@ class ECFlexpart: # the simplest case self.params['OG__ML'][0] += '/U/V/77' elif c.gauss == '0' and c.eta == '0': -#AP then remove?!?!?!? # this is not recommended (inaccurate) + # this is not recommended (inaccurate) self.params['OG__ML'][0] += '/U/V' elif c.gauss == '1' and c.eta == '0': # this is needed for data before 2008, or for reanalysis data @@ -323,7 +318,7 @@ class ECFlexpart: return - def write_namelist(self, c, filename): #done + def write_namelist(self, c, filename): ''' @Description: Creates a namelist file in the temporary directory and writes @@ -335,8 +330,8 @@ class ECFlexpart: self: instance of ECFlexpart The current object of the class. - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL files, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -382,49 +377,41 @@ class ECFlexpart: return - def retrieve(self, server, dates, times, inputdir=''): + def retrieve(self, server, dates, inputdir='.'): ''' @Description: - + Finalizing the retrieval information by setting final details + depending on grid type. + Prepares MARS retrievals per grid type and submits them. @Input: self: instance of ECFlexpart + The current object of the class. - server: instance of ECMWFService - - dates: + server: instance of ECMWFService or ECMWFDataServer + The connection to the ECMWF server. This is different + for member state users which have full access and non + member state users which have only access to the public + data sets. The decision is made from command line argument + "public"; for public access its True (ECMWFDataServer) + for member state users its False (ECMWFService) - times: + dates: string + Contains start and end date of the retrieval in the format + "YYYYMMDD/to/YYYYMMDD" inputdir: string, optional - Default string is empty (''). + Path to the directory where the retrieved data is about + to be stored. The default is the current directory ('.'). @Return: <nothing> ''' self.dates = dates self.server = server - - if inputdir == "": - self.inputdir = '.' - else: - self.inputdir = inputdir - - # Retrieve Q not for using Q but as a template for a reduced gaussian - # grid one date and time is enough - # Take analysis at 00 - qdate = self.dates - idx = qdate.find("/") - if (idx > 0): - qdate = self.dates[:idx] - - #QG = MARSretrieval(self.server, marsclass = self.marsclass, stream = self.stream, type = "an", levtype = "ML", levelist = "1", - #gaussian = "reduced",grid = '{}'.format((int(self.resol)+1)/2), resol = self.resol,accuracy = self.accuracy,target = self.inputdir+"/"+"QG.grb", - #date = qdate, time = "00",expver = self.expver, param = "133.128") - #QG.displayInfo() - #QG.dataRetrieve() - + self.inputdir = inputdir oro = False + for ftype in self.types: for pk, pv in self.params.iteritems(): if isinstance(pv, str): @@ -452,7 +439,6 @@ class ECFlexpart: oro = True else: continue - if pk == 'GG__SL' and pv[0] == 'Q': area = "" gaussian = 'reduced' @@ -460,6 +446,7 @@ class ECFlexpart: area = self.area gaussian = self.gaussian + # ------ on demand path -------------------------------------------------- if self.basetime is None: MR = MARSretrieval(self.server, marsclass=self.marsclass, stream=mfstream, @@ -472,35 +459,39 @@ class ECFlexpart: MR.displayInfo() MR.dataRetrieve() - # The whole else section is only necessary for operational scripts. - # It could be removed + # ------ operational path ------------------------------------------------ else: # check if mars job requests fields beyond basetime. # If yes eliminate those fields since they may not # be accessible with user's credentials - sm1 = -1 if 'by' in mfstep: sm1 = 2 - tm1 = -1 + else: + sm1 = -1 + if 'by' in mftime: tm1 = 2 + else: + tm1 = -1 + maxtime = datetime.datetime.strptime( mfdate.split('/')[-1] + mftime.split('/')[tm1], '%Y%m%d%H') + datetime.timedelta( hours=int(mfstep.split('/')[sm1])) - elimit = datetime.datetime.strptime( mfdate.split('/')[-1] + self.basetime, '%Y%m%d%H') if self.basetime == '12': + # -------------- flux data ---------------------------- if 'acc' in pk: - # Strategy: if maxtime-elimit> = 24h reduce date by 1, - # if 12h< = maxtime-elimit<12h reduce time for last date - # if maxtime-elimit<12h reduce step for last time - # A split of the MARS job into 2 is likely necessary. - maxtime = elimit-datetime.timedelta(hours=24) + # Strategy: + # if maxtime-elimit >= 24h reduce date by 1, + # if 12h <= maxtime-elimit<12h reduce time for last date + # if maxtime-elimit<12h reduce step for last time + # A split of the MARS job into 2 is likely necessary. + maxtime = elimit - datetime.timedelta(hours=24) mfdate = '/'.join(('/'.join(mfdate.split('/')[:-1]), datetime.datetime.strftime( maxtime, '%Y%m%d'))) @@ -540,6 +531,7 @@ class ECFlexpart: MR.displayInfo() MR.dataRetrieve() + # -------------- non flux data ------------------------ else: MR = MARSretrieval(self.server, marsclass=self.marsclass, @@ -554,10 +546,10 @@ class ECFlexpart: MR.displayInfo() MR.dataRetrieve() - else: + else: # basetime == 0 ??? #AP + maxtime = elimit - datetime.timedelta(hours=24) mfdate = datetime.datetime.strftime(maxtime,'%Y%m%d') - mftimesave = ''.join(mftime) if '/' in mftime: @@ -614,14 +606,14 @@ class ECFlexpart: return - def process_output(self, c): #done + def process_output(self, c): ''' @Description: - The grib files are postprocessed depending on selection in - control file. The resulting files are moved to the output + The grib files are postprocessed depending on the selection in + CONTROL file. The resulting files are moved to the output directory if its not equla to the input directory. The following modifications might be done if - properly switched in control file: + properly switched in CONTROL file: GRIB2 - Conversion to GRIB2 ECTRANS - Transfer of files to gateway server ECSTORAGE - Storage at ECMWF server @@ -631,8 +623,8 @@ class ECFlexpart: self: instance of ECFlexpart The current object of the class. - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL file, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -649,7 +641,7 @@ class ECFlexpart: ''' - print('Postprocessing:\n Format: {}\n'.format(c.format)) + print('\n\nPostprocessing:\n Format: {}\n'.format(c.format)) if c.ecapi is False: print('ecstorage: {}\n ecfsdir: {}\n'. @@ -674,14 +666,14 @@ class ECFlexpart: if int(c.ectrans) == 1 and c.ecapi is False: for ofile in self.outputfilelist: p = subprocess.check_call(['ectrans', '-overwrite', '-gateway', - c.gateway, '-remote', c.destination, - '-source', ofile]) + c.gateway, '-remote', c.destination, + '-source', ofile]) print('ectrans:', p) if int(c.ecstorage) == 1 and c.ecapi is False: for ofile in self.outputfilelist: p = subprocess.check_call(['ecp', '-o', ofile, - os.path.expandvars(c.ecfsdir)]) + os.path.expandvars(c.ecfsdir)]) if c.outputdir != c.inputdir: for ofile in self.outputfilelist: @@ -692,7 +684,7 @@ class ECFlexpart: if c.grib2flexpart == '1': # generate AVAILABLE file - # Example of AVAILABLE file data + # Example of AVAILABLE file data: # 20131107 000000 EN13110700 ON DISC clist = [] for ofile in self.outputfilelist: @@ -741,8 +733,7 @@ class ECFlexpart: break i += 1 -# clist.sort() - # insert the date and time information of run star and end + # insert the date and time information of run start and end # into the list of lines of COMMAND file lflist = lflist[:i+1] + \ [clist[0][:16], clist[-1][:16]] + \ @@ -752,8 +743,7 @@ class ECFlexpart: with open(pwd + '/Options/COMMAND', 'w') as g: g.write('\n'.join(lflist) + '\n') - # change to outputdir and start the - # grib2flexpart run + # change to outputdir and start the grib2flexpart run # afterwards switch back to the working dir os.chdir(c.outputdir) p = subprocess.check_call([os.path.expandvars( @@ -764,7 +754,7 @@ class ECFlexpart: return - def create(self, inputfiles, c): #done + def create(self, inputfiles, c): ''' @Description: This method is based on the ECMWF example index.py @@ -772,11 +762,12 @@ class ECFlexpart: An index file will be created which depends on the combination of "date", "time" and "stepRange" values. This is used to iterate - over all messages in the grib files passed through the parameter - "inputfiles" to seperate specific parameters into fort.* files. - Afterwards the FORTRAN program Convert2 is called to convert + over all messages in each grib file which were passed through the + parameter "inputfiles" to seperate specific parameters into fort.* + files. Afterwards the FORTRAN program Convert2 is called to convert the data fields all to the same grid and put them in one file - per day. + per unique time step (combination of "date", "time" and + "stepRange"). @Input: self: instance of ECFlexpart @@ -785,8 +776,8 @@ class ECFlexpart: inputfiles: instance of UIOFiles Contains a list of files. - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL files, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -825,14 +816,12 @@ class ECFlexpart: # index_vals[1]: ('0', '1200', '1800', '600') ; time # index_vals[2]: ('0', '12', '3', '6', '9') ; stepRange - # delete old fort.* files and open them newly fdict = {'10':None, '11':None, '12':None, '13':None, '16':None, '17':None, '19':None, '21':None, '22':None, '20':None} - #for f in fdict.keys(): - # silentremove(c.inputdir + "/fort." + f) - for prod in product(*index_vals): + # flag for Fortran program CONVERT2, initially False + convertFlag = False print 'current prod: ', prod # e.g. prod = ('20170505', '0', '12') # ( date ,time, step) @@ -841,11 +830,16 @@ class ECFlexpart: for i in range(len(index_keys)): grib_index_select(iid, index_keys[i], prod[i]) + # get first id from current product gid = grib_new_from_index(iid) - # do convert2 program if gid at this time is not None, - # therefore save in hid - hid = gid + + # if there is data for this product combination + # prepare some date and time parameter before reading the data if gid is not None: + # Fortran program CONVERT2 is only done if gid at this time is + # not None, therefore save information in convertFlag + convertFlag = True + # remove old fort.* files and open new ones for k, f in fdict.iteritems(): silentremove(c.inputdir + "/fort." + k) fdict[k] = open(c.inputdir + '/fort.' + k, 'w') @@ -899,7 +893,7 @@ class ECFlexpart: except AttributeError: pass - + # helper variable to remember which fields are already used. savedfields = [] while 1: if gid is None: @@ -923,8 +917,8 @@ class ECFlexpart: grib_write(gid, fdict['12']) elif paramId == 155 and gridtype == 'sh': grib_write(gid, fdict['13']) - elif paramId in [129, 138, 155] and levtype == 'hybrid' \ - and c.wrf == '1': + elif paramId in [129, 138, 155] and levtype == 'hybrid' \ + and c.wrf == '1': pass elif paramId == 246 or paramId == 247: # cloud liquid water and ice @@ -948,11 +942,10 @@ class ECFlexpart: try: if c.wrf == '1': -# die if abfrage scheint ueberfluessig da eh das gleihce ausgefuehrt wird - if levtype == 'hybrid': + if levtype == 'hybrid': # model layer if paramId in [129, 130, 131, 132, 133, 138, 155]: grib_write(gid, fwrf) - else: + else: # sfc layer if paramId in wrfpars: grib_write(gid, fwrf) except AttributeError: @@ -964,10 +957,8 @@ class ECFlexpart: for f in fdict.values(): f.close() - # call for CONVERT2 -# AUSLAGERN IN EIGENE FUNKTION - - if hid is not None: + # call for CONVERT2 if flag is True + if convertFlag: pwd = os.getcwd() os.chdir(c.inputdir) if os.stat('fort.21').st_size == 0 and int(c.eta) == 1: @@ -977,37 +968,41 @@ class ECFlexpart: myerror(c, 'fort.21 is empty while parameter eta is set \ to 1 in CONTROL file') + # create the corresponding output file fort.15 + # (generated by CONVERT2) + fort.16 (paramId 167 and 168) p = subprocess.check_call([os.path.expandvars( os.path.expanduser(c.exedir)) + '/CONVERT2'], shell=True) os.chdir(pwd) - # create the corresponding output file fort.15 - # (generated by CONVERT2) - # + fort.16 (paramId 167 and paramId 168) + + # create final output filename, e.g. EN13040500 (ENYYMMDDHH) fnout = c.inputdir + '/' + c.prefix if c.maxstep > 12: suffix = cdate[2:8] + '.{:0>2}'.format(time/100) + \ '.{:0>3}'.format(step) else: suffix = cdateH[2:10] - fnout += suffix print("outputfile = " + fnout) self.outputfilelist.append(fnout) # needed for final processing - fout = open(fnout, 'wb') - shutil.copyfileobj(open(c.inputdir + '/fort.15', 'rb'), fout) - if c.cwc == '1': - shutil.copyfileobj(open(c.inputdir + '/fort.22', 'rb'), fout) - shutil.copyfileobj(open(c.inputdir + '/flux' + cdate[0:2] + - suffix, 'rb'), fout) - shutil.copyfileobj(open(c.inputdir + '/fort.16', 'rb'), fout) - orolsm = glob.glob(c.inputdir + - '/OG_OROLSM__SL.*.' + c.ppid + '*')[0] - shutil.copyfileobj(open(orolsm, 'rb'), fout) - fout.close() + + # create outputfile and copy all data from intermediate files + # to the outputfile (final GRIB files) + orolsm = os.path.basename(glob.glob( + c.inputdir + '/OG_OROLSM__SL.*.' + c.ppid + '*')[0]) + fluxfile = 'flux' + cdate[0:2] + suffix + if c.cwc != '1': + flist = ['fort.15', fluxfile, 'fort.16', orolsm] + else: + flist = ['fort.15', 'fort.22', fluxfile, 'fort.16', orolsm] + + with open(fnout, 'wb') as fout: + for f in flist: + shutil.copyfileobj(open(c.inputdir + '/' + f, 'rb'), fout) + if c.omega == '1': - fnout = c.outputdir + '/OMEGA' - fout = open(fnout, 'wb') - shutil.copyfileobj(open(c.inputdir + '/fort.25', 'rb'), fout) + with open(c.outputdir + '/OMEGA', 'wb') as fout: + shutil.copyfileobj( + open(c.inputdir + '/fort.25', 'rb'), fout) try: if c.wrf == '1': @@ -1019,11 +1014,14 @@ class ECFlexpart: return - def deacc_fluxes(self, inputfiles, c): ''' @Description: - + Goes through all flux fields in ordered time and de-accumulate + the fields. Afterwards the fields are disaggregated in time. + Different versions of disaggregation is provided for rainfall + data (darain, modified linear) and the surface fluxes and + stress data (dapoly, cubic polynomial). @Input: self: instance of ECFlexpart @@ -1032,8 +1030,8 @@ class ECFlexpart: inputfiles: instance of UIOFiles Contains a list of files. - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL file, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -1132,6 +1130,7 @@ class ECFlexpart: hnout = c.inputdir + '/flux' + sdates.strftime('%Y%m%d%H') g = open(gnout, 'w') h = open(hnout, 'w') + print("outputfile = " + fnout) f = open(fnout, 'w') diff --git a/python/ECMWF_ENV b/python/ECMWF_ENV deleted file mode 100644 index 6706466..0000000 --- a/python/ECMWF_ENV +++ /dev/null @@ -1,4 +0,0 @@ -ECUID km4a -ECGID at -GATEWAY srvx8.img.univie.ac.at -DESTINATION philipa8@genericSftp diff --git a/python/GribTools.py b/python/GribTools.py index 3029352..5c2a925 100644 --- a/python/GribTools.py +++ b/python/GribTools.py @@ -2,48 +2,52 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -#AP # - GribTools name möglicherweise etwas verwirrend. # - change self.filename in self.filenames!!! -# - add file description # - bis auf --init-- und index wird keine Funktion verwendet!? #************************************************************************ -""" -@Author: Anne Fouilloux (University of Oslo) - -@Date: July 2014 - -@ChangeHistory: - February 2018 - Anne Philipp (University of Vienna): - - applied PEP8 style guide - - added documentation - - changed some naming - -@License: - (C) Copyright 2014-2018. - - 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. - -@Requirements: - - A standard python 2.6 or 2.7 installation - - dateutils - - ECMWF specific packages, all available from https://software.ecmwf.int/ - ECMWF WebMARS, gribAPI with python enabled, emoslib and - ecaccess web toolkit - -@Description: - Further documentation may be obtained from www.flexpart.eu. +#******************************************************************************* +# @Author: Anne Fouilloux (University of Oslo) +# +# @Date: July 2014 +# +# @Change History: +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation +# - changed some naming +# +# @License: +# (C) Copyright 2014-2018. +# +# 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. +# +# @Class Description: +# The GRIB API provides all necessary tools to work directly with the +# grib files. Nevertheless, the GRIB API tools are very basic and are in +# direct connection with the grib files. This class provides some higher +# functions which apply a set of GRIB API tools together in the respective +# context. So, the class initially contains a list of grib files (their +# names) and the using program then applies the methods directly on the +# class objects without having to think about how the actual GRIB API +# tools have to be arranged. +# +# @Class Content: +# - __init__ +# - getkeys +# - setkeys +# - copy +# - index +# +#******************************************************************************* - ... -""" # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ -from gribapi import * -import traceback -import sys import os +from gribapi import * + # ------------------------------------------------------------------------------ # CLASS # ------------------------------------------------------------------------------ diff --git a/python/MARSretrieval.py b/python/MARSretrieval.py index e5b6d06..3c1c5b3 100644 --- a/python/MARSretrieval.py +++ b/python/MARSretrieval.py @@ -2,11 +2,46 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -#AP -# - # - #************************************************************************ -## ------------------------------------------------------------------------------ +#******************************************************************************* +# @Author: Anne Fouilloux (University of Oslo) +# +# @Date: October 2014 +# +# @Change History: +# +# November 2015 - Leopold Haimberger (University of Vienna): +# - optimized displayInfo +# - optimized dataRetrieve and seperate between python and shell +# script call +# +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation +# - applied some minor modifications in programming style/structure +# +# @License: +# (C) Copyright 2015-2018. +# +# 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. +# +# @Class Description: +# A MARS revtrieval has a specific syntax with a selection of keywords and +# their corresponding values. This class provides the necessary functions +# by displaying the selected parameters and their values and the actual +# retrievement of the data through a mars request or a Python web api +# interface. The initialization already expects all the keyword values. +# +# @Class Content: +# - __init__ +# - displayInfo +# - dataRetrieve +# +#******************************************************************************* + +# ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ import subprocess @@ -17,7 +52,7 @@ try: import ecmwfapi except ImportError: ecapi = False -#from gribapi import * + # ------------------------------------------------------------------------------ # CLASS # ------------------------------------------------------------------------------ diff --git a/python/Tools.py b/python/Tools.py index 9d55a6b..24e9e02 100644 --- a/python/Tools.py +++ b/python/Tools.py @@ -2,23 +2,67 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -#AP -# - +# - check myerror +# - check normalexit +# - check getListAsString +# - seperate args and control interpretation #************************************************************************ -""" +#******************************************************************************* +# @Author: Anne Philipp (University of Vienna) +# +# @Date: May 2018 +# +# @Change History: +# October 2014 - Anne Fouilloux (University of Oslo) +# - created functions silentremove and product (taken from ECMWF) +# +# November 2015 - Leopold Haimberger (University of Vienna) +# - created functions: interpret_args_and_control, cleanup +# myerror, normalexit, init128, toparamId +# +# April 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation +# - moved all functions from file FlexpartTools to this file Tools +# - added function getListAsString +# +# @License: +# (C) Copyright 2014-2018. +# +# 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. +# +# @Modul Description: +# This module contains a couple of helpful functions which are +# used in different places in flex_extract. +# +# @Module Content: +# - interpret_args_and_control +# - cleanup +# - myerror +# - normalexit +# - product +# - silentremove +# - init128 +# - toparamId +# - getListAsString +# +#******************************************************************************* -""" # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ -from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter import os import errno import sys import glob +import traceback from numpy import * from gribapi import * -import Control +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter + +# software specific class from flex_extract +from ControlFile import ControlFile # ------------------------------------------------------------------------------ # FUNCTIONS @@ -27,7 +71,7 @@ import Control def interpret_args_and_control(): ''' @Description: - Assigns the command line arguments and reads control file + Assigns the command line arguments and reads CONTROL file content. Apply default values for non mentioned arguments. @Input: @@ -37,8 +81,8 @@ def interpret_args_and_control(): args: instance of ArgumentParser Contains the commandline arguments from script/program call. - c: instance of class Control - Contains all necessary information of a control file. The parameters + c: instance of class ControlFile + Contains all necessary information of a CONTROL file. The parameters are: DAY1, DAY2, DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS, STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, @@ -61,7 +105,7 @@ def interpret_args_and_control(): parser.add_argument("--date_chunk", dest="date_chunk", default=None, help="# of days to be retrieved at once") - # some arguments that override the default in the control file + # some arguments that override the default in the CONTROL file parser.add_argument("--basetime", dest="basetime", help="base such as 00/12 (for half day retrievals)") parser.add_argument("--step", dest="step", @@ -95,21 +139,21 @@ def interpret_args_and_control(): (e.g. ecgate or cca )") parser.add_argument("--controlfile", dest="controlfile", default='CONTROL.temp', - help="file with control parameters") + help="file with CONTROL parameters") parser.add_argument("--debug", dest="debug", default=0, help="Debug mode - leave temporary files intact") args = parser.parse_args() - # create instance of Control for specified controlfile + # create instance of ControlFile for specified controlfile # and assign the parameters (and default values if necessary) try: - c = Control.Control(args.controlfile) + c = ControlFile(args.controlfile) except IOError: try: - c = Control.Control(localpythonpath + args.controlfile) + c = ControlFile(localpythonpath + args.controlfile) except: - print('Could not read control file "' + args.controlfile + '"') + print('Could not read CONTROL file "' + args.controlfile + '"') print('Either it does not exist or its syntax is wrong.') print('Try "' + sys.argv[0].split('/')[-1] + ' -h" to print usage information') @@ -118,12 +162,12 @@ def interpret_args_and_control(): # check for having at least a starting date if args.start_date is None and getattr(c, 'start_date') is None: print('start_date specified neither in command line nor \ - in control file ' + args.controlfile) + in CONTROL file ' + args.controlfile) print('Try "' + sys.argv[0].split('/')[-1] + ' -h" to print usage information') exit(1) - # save all existing command line parameter to the Control instance + # save all existing command line parameter to the ControlFile instance # if parameter is not specified through the command line or CONTROL file # set default values if args.start_date is not None: @@ -198,11 +242,11 @@ def cleanup(c): ''' @Description: Remove all files from intermediate directory - (inputdir from control file). + (inputdir from CONTROL file). @Input: - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL file, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -239,8 +283,8 @@ def myerror(c, message='ERROR'): before exiting the program. @Input: - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL file, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -289,8 +333,8 @@ def normalexit(c, message='Done!'): Prints a specific exit message which can be passed to the function. @Input: - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL file, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -356,7 +400,6 @@ def product(*args, **kwds): Return will be done with "yield". A tuple of combined arguments. See example in description above. ''' - pools = map(tuple, args) * kwds.get('repeat', 1) result = [[]] for pool in pools: @@ -370,9 +413,8 @@ def product(*args, **kwds): def silentremove(filename): ''' @Description: - Removes the file which name is passed to the function if - it exists. The function does not fail if the file does not - exist. + If "filename" exists , it is removed. + The function does not fail if the file does not exist. @Input: filename: string @@ -425,7 +467,7 @@ def toparamId(pars, table): @Input: pars: string - Addpar argument from control file in the format of + Addpar argument from CONTROL file in the format of parameter names instead of ids. The parameter short names are sepearted with "/" and they are passed as one single string. @@ -437,7 +479,7 @@ def toparamId(pars, table): @Return: ipar: list of integer - List of addpar parameters from control file transformed to + List of addpar parameters from CONTROL file transformed to parameter ids in the format of integer. ''' cpar = pars.upper().split('/') @@ -454,8 +496,20 @@ def toparamId(pars, table): return ipar -def getListAsString(listobj): +def getListAsString(listObj): ''' @Description: + Converts a list of arbitrary content into a single string. + + @Input: + listObj: list + A list with arbitrary content. + + @Return: + strOfList: string + The content of the list as a single string. ''' - return ", ".join( str(l) for l in listobj) \ No newline at end of file + + strOfList = ", ".join( str(l) for l in listObj) + + return strOfList diff --git a/python/UIOFiles.py b/python/UIOFiles.py index faeead6..7b01333 100644 --- a/python/UIOFiles.py +++ b/python/UIOFiles.py @@ -2,40 +2,48 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -#AP # - checken welche regelmässigen methoden auf diese Files noch angewendet werden # und dann hier implementieren -# - add description of file! +# cleanup hier rein #************************************************************************ -""" -@Author: Anne Fouilloux (University of Oslo) +#******************************************************************************* +# @Author: Anne Fouilloux (University of Oslo) +# +# @Date: October 2014 +# +# @Change History: +# +# November 2015 - Leopold Haimberger (University of Vienna): +# - modified method listFiles to work with glob instead of listdir +# - added pattern search in method listFiles +# +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation +# - optimisation of method listFiles since it didn't work correctly +# for sub directories +# - additional speed up of method listFiles +# - modified the class so that it is initiated with a pattern instead +# of suffixes. Gives more precision in selection of files. +# +# @License: +# (C) Copyright 2014-2018. +# +# 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. +# +# @Class Decription: +# The class is for file manipulation. It is initiated with a regular +# expression pattern for this instance and can produce a list of Files +# from the given file pattern. These files can be deleted. +# +# @Class Content: +# - __init__ +# - listFiles +# - deleteFiles +# +#******************************************************************************* -@Date: October 2014 - -@ChangeHistory: - November 2015 - Leopold Haimberger (University of Vienna): - - modified method listFiles to work with glob instead of listdir - - added pattern search in method listFiles - - February 2018 - Anne Philipp (University of Vienna): - - applied PEP8 style guide - - added documentation - - optimisation of method listFiles since it didn't work correctly - for sub directories - - additional speed up of method listFiles - -@License: - (C) Copyright 2014-2018. - - 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. - -@Requirements: - A standard python 2.6 or 2.7 installation - -@Description: - ... -""" # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ @@ -43,48 +51,50 @@ import os import glob import fnmatch import time + +# software specific module from flex_extract import profiling +from Tools import silentremove + # ------------------------------------------------------------------------------ # CLASS # ------------------------------------------------------------------------------ + class UIOFiles: ''' Class to manipulate files. At initialisation it has the attribute - suffix which stores a list of suffixes of the files associated + pattern which stores a regular expression pattern for the files associated with the instance of the class. ''' # -------------------------------------------------------------------------- # CLASS FUNCTIONS # -------------------------------------------------------------------------- - def __init__(self, suffix): + def __init__(self, pattern): ''' @Description: - Assignes the suffixes of the files which should be - associated with the instance of the class. + Assignes a specific pattern for these files. @Input: self: instance of UIOFiles Description see class documentation. - suffix: list of strings - The types of files which should be manipulated such as - ['grib', 'grb', 'grib1', 'grib2', 'grb1', 'grb2'] + pattern: string + Regular expression pattern. For example: '*.grb' @Return: <nothing> ''' - self.suffix = suffix + self.pattern = pattern return #@profiling.timefn - def listFiles(self, path, pattern, callid=0): + def listFiles(self, path, callid=0): ''' @Description: Lists all files in the directory with the matching - regular expression pattern. The suffixes are already stored - in a list attribute "suffix". + regular expression pattern. @Input: self: instance of UIOFiles @@ -93,10 +103,6 @@ class UIOFiles: path: string Directory where to list the files. - pattern: string - Regular expression pattern. For example: - '*OG_acc_SL*.'+c.ppid+'.*' - callid: integer Id which tells the function if its the first call or a recursive call. Default and first call is 0. @@ -114,10 +120,9 @@ class UIOFiles: path = os.path.abspath(path) # get the file list of the path if its not a directory and - # if it contains one of the suffixes + # if it contains the pattern self.files.extend([os.path.join(path, k) for k in os.listdir(path) - if fnmatch.fnmatch(k, pattern) and - os.path.splitext(k)[-1] in self.suffix]) + if fnmatch.fnmatch(k, self.pattern)]) # find possible sub-directories in the path subdirs = [s for s in os.listdir(path) @@ -126,6 +131,24 @@ class UIOFiles: # do recursive calls for sub-direcorties if subdirs: for subdir in subdirs: - self.listFiles(os.path.join(path, subdir), pattern, callid=1) + self.listFiles(os.path.join(path, subdir), callid=1) + + return + + def deleteFiles(self): + ''' + @Description: + Deletes the files. + + @Input: + self: instance of UIOFiles + Description see class documentation. + + @Return: + <nothing> + ''' + + for f in self.files: + silentremove(f) return diff --git a/python/compilejob.ksh b/python/compilejob.ksh index d276521..3afc4e4 100644 --- a/python/compilejob.ksh +++ b/python/compilejob.ksh @@ -4,7 +4,7 @@ # start with ecaccess-job-submit -queueName ecgb NAME_OF_THIS_FILE on gateway server # start with sbatch NAME_OF_THIS_FILE directly on machine -#SBATCH --workdir=/scratch/ms/at/km4a +#SBATCH --workdir=/scratch/ms/spatlh00/lh0 #SBATCH --qos=normal #SBATCH --job-name=flex_ecmwf #SBATCH --output=flex_ecmwf.%j.out @@ -32,7 +32,7 @@ case $HOST in module unload emos module load grib_api/1.14.5 module load emos/437-r64 -export FLEXPART_ROOT_SCRIPTS=${HOME} +export FLEXPART_ROOT_SCRIPTS=$HOME # export ECMWFDATA=$FLEXPART_ROOT/ECMWFDATA$VERSION # export PYTHONPATH=$ECMWFDATA/python # export PATH=${PATH}:$ECMWFDATA/python @@ -48,7 +48,7 @@ export FLEXPART_ROOT_SCRIPTS=${HOME} echo $HOME | awk -F / '{print $1, $2, $3, $4}' export GROUP=`echo $HOME | awk -F / '{print $4}'` export SCRATCH=/scratch/ms/${GROUP}/${USER} -export FLEXPART_ROOT_SCRIPTS=${HOME} +export FLEXPART_ROOT_SCRIPTS=$HOME # export ECMWFDATA=$FLEXPART_ROOT/ECMWFDATA$VERSION # export PYTHONPATH=$ECMWFDATA/python # export PATH=${PATH}:$ECMWFDATA/python diff --git a/python/getMARSdata.py b/python/getMARSdata.py index 278176d..43872cb 100755 --- a/python/getMARSdata.py +++ b/python/getMARSdata.py @@ -2,82 +2,130 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -# - Change History ist nicht angepasst ans File! -# - add file description +# - add function docstrings!!!! #************************************************************************ -""" -@Author: Anne Fouilloux (University of Oslo) - -@Date: October 2014 - -@ChangeHistory: - November 2015 - Leopold Haimberger (University of Vienna): - - using the WebAPI also for general MARS retrievals - - job submission on ecgate and cca - - job templates suitable for twice daily operational dissemination - - dividing retrievals of longer periods into digestable chunks - - retrieve also longer term forecasts, not only analyses and - short term forecast data - - conversion into GRIB2 - - conversion into .fp format for faster execution of FLEXPART - - February 2018 - Anne Philipp (University of Vienna): - - applied PEP8 style guide - - added documentation - - minor changes in programming style for consistence - -@License: - (C) Copyright 2014-2018. - - 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. - -@Requirements: - - A standard python 2.6 or 2.7 installation - - dateutils - - ECMWF specific packages, all available from https://software.ecmwf.int/ - ECMWF WebMARS, gribAPI with python enabled, emoslib and - ecaccess web toolkit - -@Description: - Further documentation may be obtained from www.flexpart.eu. - -""" +#******************************************************************************* +# @Author: Anne Fouilloux (University of Oslo) +# +# @Date: October 2014 +# +# @Change History: +# +# November 2015 - Leopold Haimberger (University of Vienna): +# - moved the getEIdata program into a function "getMARSdata" +# - moved the AgurmentParser into a seperate function +# - adatpted the function for the use in flex_extract +# - renamed file to getMARSdata +# +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added structured documentation +# - minor changes in programming style for consistence +# - added function main and moved function calls vom __main__ there +# (necessary for better documentation with docstrings for later +# online documentation) +# - use of UIFiles class for file selection and deletion + +# +# @License: +# (C) Copyright 2014-2018. +# +# 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. +# +# @Program Functionality: +# This program can be used as a module in the whole flex_extract process +# or can be run by itself to just extract MARS data from ECMWF. To do so, +# a couple of necessary parameters has to be passed with the program call. +# See documentation for more details. +# +# @Program Content: +# - main +# - getMARSdata +# +#******************************************************************************* + # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ +import os +import sys +import datetime +import inspect try: ecapi=True import ecmwfapi except ImportError: ecapi=False -import calendar -import shutil -import datetime -import time -import os -import glob -import sys -import inspect -# add path to submit.py to pythonpath so that python finds its buddies -localpythonpath=os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) +# add path to pythonpath so that python finds its buddies +localpythonpath = os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) if localpythonpath not in sys.path: sys.path.append(localpythonpath) -from Control import Control +# software specific classes and modules from flex_extract +from ControlFile import ControlFile from Tools import myerror, normalexit, \ interpret_args_and_control from ECFlexpart import ECFlexpart +from UIOFiles import UIOFiles # ------------------------------------------------------------------------------ # FUNCTION # ------------------------------------------------------------------------------ -def getMARSdata(args, c): +def main(): + ''' + @Description: + If getMARSdata is called from command line, this function controls + the program flow and calls the argumentparser function and + the getMARSdata function for retrieving EC data. + + @Input: + <nothing> + + @Return: + <nothing> + ''' + args, c = interpret_args_and_control() + getMARSdata(args, c) + normalexit(c) + return + +def getMARSdata(args, c): + ''' + @Description: + Retrieves the EC data needed for a FLEXPART simulation. + Start and end dates for retrieval period is set. Retrievals + are divided into smaller periods if necessary and datechunk parameter + is set. + + @Input: + args: instance of ArgumentParser + Contains the commandline arguments from script/program call. + + c: instance of class ControlFile + Contains all the parameters of CONTROL file, which are e.g.: + DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, + STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, + LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, + OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, SMOOTH, FORMAT, + ADDPAR, WRF, CWC, PREFIX, ECSTORAGE, ECTRANS, ECFSDIR, + MAILOPS, MAILFAIL, GRIB2FLEXPART, FLEXPARTDIR, BASETIME + DATE_CHUNK, DEBUG, INPUTDIR, OUTPUTDIR, FLEXPART_ROOT_SCRIPTS + + For more information about format and content of the parameter + see documentation. + + @Return: + <nothing> + ''' if not os.path.exists(c.inputdir): os.makedirs(c.inputdir) + + print("Retrieving EC data!") print("start date %s " % (c.start_date)) print("end date %s " % (c.end_date)) @@ -87,40 +135,34 @@ def getMARSdata(args, c): server = False c.ecapi = ecapi - print 'ecapi:', c.ecapi - -# Retrieve EC data for running flexpart -#AP change this variant to correct format conversion with datetime -#AP import datetime and timedelta explicitly - syear = int(c.start_date[:4]) - smonth = int(c.start_date[4:6]) - sday = int(c.start_date[6:]) - start = datetime.date(year=syear, month=smonth, day=sday) + print 'ecapi: ', c.ecapi + + # set start date of retrieval period + start = datetime.date(year=int(c.start_date[:4]), + month=int(c.start_date[4:6]), + day=int(c.start_date[6:])) startm1 = start - datetime.timedelta(days=1) if c.basetime == '00': start = startm1 - eyear = int(c.end_date[:4]) - emonth = int(c.end_date[4:6]) - eday = int(c.end_date[6:]) - end = datetime.date(year=eyear, month=emonth, day=eday) + # set end date of retrieval period + end = datetime.date(year=int(c.end_date[:4]), + month=int(c.end_date[4:6]), + day=int(c.end_date[6:])) if c.basetime == '00' or c.basetime == '12': endp1 = end + datetime.timedelta(days=1) else: endp1 = end + datetime.timedelta(days=2) + # set time period of one single retrieval datechunk = datetime.timedelta(days=int(c.date_chunk)) - # retrieving of accumulated data fields (flux data), (maximum one month) - - # remove old files - print 'removing content of ' + c.inputdir - tobecleaned = glob.glob(c.inputdir + '/*_acc_*.' + \ - str(os.getppid()) + '.*.grb') - for f in tobecleaned: - os.remove(f) + # -------------- flux data ------------------------------------------------ + print 'removing old flux content of ' + c.inputdir + tobecleaned = UIOFiles('*_acc_*.' + str(os.getppid()) + '.*.grb') + tobecleaned.listFiles(c.inputdir) + tobecleaned.deleteFiles() - times = None # if forecast for maximum one day (upto 23h) are to be retrieved, # collect accumulation data (flux data) # with additional days in the beginning and at the end @@ -128,24 +170,24 @@ def getMARSdata(args, c): if c.maxstep < 24: day = startm1 while day < endp1: - # retrieve MARS data for the whole period - flexpart = ECFlexpart(c, fluxes=True) - tmpday = day + datechunk - datetime.timedelta(days=1) - if tmpday < endp1: - dates = day.strftime("%Y%m%d") + "/to/" + \ - tmpday.strftime("%Y%m%d") - else: - dates = day.strftime("%Y%m%d") + "/to/" + \ - end.strftime("%Y%m%d") + # retrieve MARS data for the whole period + flexpart = ECFlexpart(c, fluxes=True) + tmpday = day + datechunk - datetime.timedelta(days=1) + if tmpday < endp1: + dates = day.strftime("%Y%m%d") + "/to/" + \ + tmpday.strftime("%Y%m%d") + else: + dates = day.strftime("%Y%m%d") + "/to/" + \ + end.strftime("%Y%m%d") - print "retrieve " + dates + " in dir " + c.inputdir + print "retrieve " + dates + " in dir " + c.inputdir - try: - flexpart.retrieve(server, dates, times, c.inputdir) - except IOError: - myerror(c,'MARS request failed') + try: + flexpart.retrieve(server, dates, c.inputdir) + except IOError: + myerror(c, 'MARS request failed') - day += datechunk + day += datechunk # if forecast data longer than 24h are to be retrieved, # collect accumulation data (flux data) @@ -155,38 +197,36 @@ def getMARSdata(args, c): else: day = start while day <= end: - # retrieve MARS data for the whole period - flexpart = ECFlexpart(c, fluxes=True) - tmpday = day + datechunk - datetime.timedelta(days=1) - if tmpday < end: - dates = day.strftime("%Y%m%d") + "/to/" + \ - tmpday.trftime("%Y%m%d") - else: - dates = day.strftime("%Y%m%d") + "/to/" + \ - end.strftime("%Y%m%d") - - print "retrieve " + dates + " in dir " + c.inputdir - - try: - flexpart.retrieve(server, dates, times, c.inputdir) - except IOError: - myerror(c, 'MARS request failed') - - day += datechunk - - # retrieving of normal data fields (non flux data), (maximum one month) - - # remove old *__* files - tobecleaned = glob.glob(c.inputdir + '/*__*.' + - str(os.getppid()) + '.*.grb') - for f in tobecleaned: - os.remove(f) + # retrieve MARS data for the whole period + flexpart = ECFlexpart(c, fluxes=True) + tmpday = day + datechunk - datetime.timedelta(days=1) + if tmpday < end: + dates = day.strftime("%Y%m%d") + "/to/" + \ + tmpday.trftime("%Y%m%d") + else: + dates = day.strftime("%Y%m%d") + "/to/" + \ + end.strftime("%Y%m%d") + + print "retrieve " + dates + " in dir " + c.inputdir + + try: + flexpart.retrieve(server, dates, c.inputdir) + except IOError: + myerror(c, 'MARS request failed') + + day += datechunk + + # -------------- non flux data -------------------------------------------- + print 'removing old non flux content of ' + c.inputdir + tobecleaned = UIOFiles('*__*.' + str(os.getppid()) + '.*.grb') + tobecleaned.listFiles(c.inputdir) + tobecleaned.deleteFiles() + day = start - times = None while day <= end: - # retrieve MARS data for the whole period + # retrieve all non flux MARS data for the whole period flexpart = ECFlexpart(c, fluxes=False) - tmpday = day+datechunk-datetime.timedelta(days=1) + tmpday = day + datechunk - datetime.timedelta(days=1) if tmpday < end: dates = day.strftime("%Y%m%d") + "/to/" + \ tmpday.strftime("%Y%m%d") @@ -197,7 +237,7 @@ def getMARSdata(args, c): print "retrieve " + dates + " in dir " + c.inputdir try: - flexpart.retrieve(server, dates, times, c.inputdir) + flexpart.retrieve(server, dates, c.inputdir) except IOError: myerror(c, 'MARS request failed') @@ -206,7 +246,5 @@ def getMARSdata(args, c): return if __name__ == "__main__": + main() - args, c = interpret_args_and_control() - getMARSdata(args, c) - normalexit(c) diff --git a/python/install.py b/python/install.py index d73b08c..09cc622 100755 --- a/python/install.py +++ b/python/install.py @@ -2,84 +2,100 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -#AP -# - Functionality Provided is not correct for this file # - localpythonpath should not be set in module load section! # - create a class Installation and divide installation in 3 subdefs for # ecgate, local and cca seperatly # - Change History ist nicht angepasst ans File! Original geben lassen #************************************************************************ -""" -@Author: Anne Fouilloux (University of Oslo) - -@Date: October 2014 - -@ChangeHistory: - November 2015 - Leopold Haimberger (University of Vienna): - - using the WebAPI also for general MARS retrievals - - job submission on ecgate and cca - - job templates suitable for twice daily operational dissemination - - dividing retrievals of longer periods into digestable chunks - - retrieve also longer term forecasts, not only analyses and - short term forecast data - - conversion into GRIB2 - - conversion into .fp format for faster execution of FLEXPART - - February 2018 - Anne Philipp (University of Vienna): - - applied PEP8 style guide - - added documentation - -@License: - (C) Copyright 2014 UIO. - - 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. - -@Requirements: - - A standard python 2.6 or 2.7 installation - - dateutils - - matplotlib (optional, for debugging) - - ECMWF specific packages, all available from https://software.ecmwf.int/ - ECMWF WebMARS, gribAPI with python enabled, emoslib and - ecaccess web toolkit - -@Description: - Further documentation may be obtained from www.flexpart.eu. - - Functionality provided: - Prepare input 3D-wind fields in hybrid coordinates + - surface fields for FLEXPART runs -""" +#******************************************************************************* +# @Author: Leopold Haimberger (University of Vienna) +# +# @Date: November 2015 +# +# @Change History: +# +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation +# +# @License: +# (C) Copyright 2015-2018. +# +# 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. +# +# @Program Functionality: +# Depending on the selected installation environment (locally or on the +# ECMWF server ecgate or cca) the program extracts the commandline +# arguments and the CONTROL file parameter and prepares the corresponding +# environment. The necessary files are collected in a tar-ball and placed +# at the target location. There its untared, the environment variables will +# be set and the Fortran code will be compiled. If the ECMWF environment is +# selected a job script is prepared and submitted for the remaining +# configurations after putting the tar-ball to the target ECMWF server. +# +# @Program Content: +# - main +# - install_args_and_control +# - install_via_gateway +# +#******************************************************************************* + # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ -import calendar -import shutil import datetime -import time -import os,sys,glob +import os +import sys +import glob import subprocess import inspect -# add path to submit.py to pythonpath so that python finds its buddies -localpythonpath=os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) -sys.path.append(localpythonpath) -from UIOFiles import UIOFiles -from string import strip from argparse import ArgumentParser,ArgumentDefaultsHelpFormatter -from GribTools import GribTools -from Control import Control -from getMARSdata import getMARSdata -from prepareFLEXPART import prepareFLEXPART -from ECFlexpart import ECFlexpart + +# add path to pythonpath so that python finds its buddies +localpythonpath = os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) +if localpythonpath not in sys.path: + sys.path.append(localpythonpath) + +# software specific classes and modules from flex_extract +from ControlFile import ControlFile # ------------------------------------------------------------------------------ # FUNCTIONS # ------------------------------------------------------------------------------ +def main(): + ''' + @Description: + Controls the installation process. Calls the installation function + if target is specified. + + @Intput: + <nothing> + + @Return: + <nothing> + ''' + + os.chdir(localpythonpath) + args, c = install_args_and_control() + + if args.install_target is not None: + install_via_gateway(c, args.install_target) + else: + print('Please specify installation target (local|ecgate|cca)') + print('use -h or --help for help') + + sys.exit() + + return + + def install_args_and_control(): ''' @Description: Assigns the command line arguments for installation and reads - control file content. Apply default values for non mentioned arguments. + CONTROL file content. Apply default values for non mentioned arguments. @Input: <nothing> @@ -88,8 +104,8 @@ def install_args_and_control(): args: instance of ArgumentParser Contains the commandline arguments from script/program call. - c: instance of class Control - Contains all necessary information of a control file. The parameters + c: instance of class ControlFile + Contains all necessary information of a CONTROL file. The parameters are: DAY1, DAY2, DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS, STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, @@ -129,14 +145,14 @@ def install_args_and_control(): parser.add_argument("--controlfile", dest="controlfile", default='CONTROL.temp', - help="file with control parameters") + help="file with CONTROL parameters") args = parser.parse_args() try: - c = Control(args.controlfile) + c = ControlFile(args.controlfile) except: - print('Could not read control file "' + args.controlfile + '"') + print('Could not read CONTROL file "' + args.controlfile + '"') print('Either it does not exist or its syntax is wrong.') print('Try "' + sys.argv[0].split('/')[-1] + ' -h" to print usage information') @@ -181,20 +197,31 @@ def install_args_and_control(): return args, c -def main(): - ''' +def install_via_gateway(c, target): ''' - os.chdir(localpythonpath) - args, c = install_args_and_control() - if args.install_target is not None: - install_via_gateway(c, args.install_target) - else: - print('Please specify installation target (local|ecgate|cca)') - print('use -h or --help for help') - sys.exit() + @Description: + Perform the actual installation on local machine or prepare data + transfer to remote gate and submit a job script which will + install everything on the remote gate. -def install_via_gateway(c, target): + @Input: + c: instance of class ControlFile + Contains all necessary information of a CONTROL file. The parameters + are: DAY1, DAY2, DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS, STREAM, + NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, + RESOL, GAUSS, ACCURACY, OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, + SMOOTH, FORMAT, ADDPAR, WRF, CWC, PREFIX, ECSTORAGE, ECTRANS, + ECFSDIR, MAILOPS, MAILFAIL, GRIB2FLEXPART, FLEXPARTDIR + For more information about format and content of the parameter see + documentation. + + target: string + The target where the installation should be processed. + E.g. "local", "ecgate" or "cca" + @Return: + <nothing> + ''' ecd = c.ecmwfdatadir template = ecd + 'python/compilejob.temp' job = ecd + 'python/compilejob.ksh' diff --git a/python/job.ksh b/python/job.ksh index 70e561c..8fba02e 100644 --- a/python/job.ksh +++ b/python/job.ksh @@ -63,13 +63,13 @@ export CONTROL=CONTROL cat >$CONTROL<<EOF GATEWAY srvx8.img.univie.ac.at -DESTINATION philipa8@genericSftp +DESTINATION annep@genericSftp accuracy 16 addpar 186 187 188 235 139 39 basetime None cwc 0 date_chunk 3 -debug 0 +debug True dpdeta 1 dtime 3 ecfsdir ectmp:/${USER}/econdemand/ diff --git a/python/job.temp b/python/job.temp index c9cff9f..4a20430 100644 --- a/python/job.temp +++ b/python/job.temp @@ -63,7 +63,7 @@ export CONTROL=CONTROL cat >$CONTROL<<EOF GATEWAY srvx8.img.univie.ac.at -DESTINATION philipa8@genericSftp +DESTINATION annep@genericSftp EOF cat >>$CONTROL<<EOF EOF diff --git a/python/joboper.ksh b/python/joboper.ksh index 94ab09f..27621bf 100644 --- a/python/joboper.ksh +++ b/python/joboper.ksh @@ -63,13 +63,13 @@ export CONTROL=CONTROL cat >$CONTROL<<EOF GATEWAY srvx8.img.univie.ac.at -DESTINATION philipa8@genericSftp +DESTINATION annep@genericSftp accuracy 16 addpar 186 187 188 235 139 39 basetime None cwc 0 date_chunk 3 -debug 0 +debug True dpdeta 1 dtime 3 ecfsdir ectmp:/${USER}/econdemand/ diff --git a/python/plot_retrieved.py b/python/plot_retrieved.py index 7d4c6ad..e34bd0d 100755 --- a/python/plot_retrieved.py +++ b/python/plot_retrieved.py @@ -1,203 +1,367 @@ #!/usr/bin/env python -# 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. +# -*- coding: utf-8 -*- +#************************************************************************ +# TODO AP +# - documentation der Funktionen +# - docu der progam functionality +# - apply pep8 +#************************************************************************ +#******************************************************************************* +# @Author: Leopold Haimberger (University of Vienna) # -# Functionality provided: Simple tool for creating maps and time series of retrieved fields. +# @Date: November 2015 # -# Requirements: -# in addition to a standard python 2.6 or 2.7 installation the following packages need to be installed -# ECMWF WebMARS, gribAPI with python enabled, emoslib, ecaccess web toolkit, all available from https://software.ecmwf.int/ -# dateutils -# matplotlib (optional, for debugging) +# @Change History: +# +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation +# - created function main and moved the two function calls for +# arguments and plotting into it +# +# @License: +# (C) Copyright 2015-2018. +# +# 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. +# +# @Program Functionality: +# Simple tool for creating maps and time series of retrieved fields. +# +# @Program Content: +# - plot_retrieved +# - plottimeseries +# - plotmap +# - interpret_plotargs +# +#******************************************************************************* +# ------------------------------------------------------------------------------ +# MODULES +# ------------------------------------------------------------------------------ import datetime import time -import os,inspect,sys,glob -import socket -# add path to submit.py to pythonpath so that python finds its buddies -localpythonpath=os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) -if localpythonpath not in sys.path: - sys.path.append(localpythonpath) +import os +import inspect +import sys +import glob +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter from matplotlib.pylab import * import matplotlib.patches as mpatches -from mpl_toolkits.basemap import Basemap,addcyclic +from mpl_toolkits.basemap import Basemap, addcyclic import matplotlib.colors as mcolors from matplotlib.font_manager import FontProperties from matplotlib.patches import Polygon import matplotlib.cm as cmx import matplotlib.colors as colors -from argparse import ArgumentParser,ArgumentDefaultsHelpFormatter - -from Tools import interpret_args_and_control, silentremove, product -from Control import Control -from GribTools import GribTools +#from rasotools.utils import stats from gribapi import * -from rasotools.utils import stats -def plot_retrieved(args,c): - - start = datetime.datetime.strptime(c.start_date,'%Y%m%d%H') - end = datetime.datetime.strptime(c.end_date,'%Y%m%d%H') +# add path to pythonpath so that python finds its buddies +localpythonpath = os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) +if localpythonpath not in sys.path: + sys.path.append(localpythonpath) - c.paramIds=asarray(c.paramIds,dtype='int') - c.levels=asarray(c.levels,dtype='int') - c.area=asarray(c.area) +# software specific classes and modules from flex_extract +from Tools import silentremove, product +from ControlFile import ControlFile +from GribTools import GribTools - index_keys=["date","time","step"] - indexfile=c.inputdir+"/date_time_stepRange.idx" +# ------------------------------------------------------------------------------ +# FUNCTION +# ------------------------------------------------------------------------------ +def main(): + ''' + @Description: + If plot_retrieved is called from command line, this function controls + the program flow and calls the argumentparser function and + the plot_retrieved function for plotting the retrieved GRIB data. + + @Input: + <nothing> + + @Return: + <nothing> + ''' + args, c = interpret_plotargs() + plot_retrieved(args, c) + + return + +def plot_retrieved(args, c): + ''' + @Description: + Reads GRIB data from a specified time period, a list of levels + and a specified list of parameter. + + @Input: + args: instance of ArgumentParser + Contains the commandline arguments from script/program call. + + c: instance of class ControlFile + Contains all necessary information of a CONTROL file. The parameters + are: DAY1, DAY2, DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS, STREAM, + NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, + RESOL, GAUSS, ACCURACY, OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, + SMOOTH, FORMAT, ADDPAR, WRF, CWC, PREFIX, ECSTORAGE, ECTRANS, + ECFSDIR, MAILOPS, MAILFAIL, GRIB2FLEXPART, DEBUG, INPUTDIR, + OUTPUTDIR, FLEXPART_ROOT_SCRIPTS + For more information about format and content of the parameter see + documentation. + + @Return: + <nothing> + ''' + start = datetime.datetime.strptime(c.start_date, '%Y%m%d%H') + end = datetime.datetime.strptime(c.end_date, '%Y%m%d%H') + + c.paramIds = asarray(c.paramIds, dtype='int') + c.levels = asarray(c.levels, dtype='int') + c.area = asarray(c.area) + + index_keys = ["date", "time", "step"] + indexfile = c.inputdir + "/date_time_stepRange.idx" silentremove(indexfile) - files=glob.glob(c.inputdir+'/'+c.prefix+'*') - grib=GribTools(files) - iid=grib.index(index_keys=index_keys, index_file = indexfile) - - gdict=dict(Ni = 360, Nj = 181, iScansNegatively = 0, jScansPositively = 0, - jPointsAreConsecutive = 0, alternativeRowScanning = 0, - latitudeOfFirstGridPointInDegrees = 90, - longitudeOfFirstGridPointInDegrees = 181, - latitudeOfLastGridPointInDegrees = -90, - longitudeOfLastGridPointInDegrees = 180, - iDirectionIncrementInDegrees = 1, - jDirectionIncrementInDegrees = 1 - ) + files = glob.glob(c.inputdir + '/' + c.prefix + '*') + grib = GribTools(files) + iid = grib.index(index_keys=index_keys, index_file = indexfile) + + gdict = dict(Ni = 360, Nj = 181, + iScansNegatively = 0, jScansPositively = 0, + jPointsAreConsecutive = 0, alternativeRowScanning = 0, + latitudeOfFirstGridPointInDegrees = 90, + longitudeOfFirstGridPointInDegrees = 181, + latitudeOfLastGridPointInDegrees = -90, + longitudeOfLastGridPointInDegrees = 180, + iDirectionIncrementInDegrees = 1, + jDirectionIncrementInDegrees = 1 + ) index_vals = [] for key in index_keys: - key_vals = grib_index_get(iid,key) + key_vals = grib_index_get(iid, key) print key_vals index_vals.append(key_vals) - fdict=dict() - fmeta=dict() - fstamp=dict() + fdict = dict() + fmeta = dict() + fstamp = dict() for p in c.paramIds: for l in c.levels: - key='{:0>3}_{:0>3}'.format(p,l) - fdict[key]=[] - fmeta[key]=[] - fstamp[key]=[] + key = '{:0>3}_{:0>3}'.format(p, l) + fdict[key] = [] + fmeta[key] = [] + fstamp[key] = [] for prod in product(*index_vals): for i in range(len(index_keys)): - grib_index_select(iid,index_keys[i],prod[i]) + grib_index_select(iid, index_keys[i], prod[i]) gid = grib_new_from_index(iid) -# if gid is not None: while(gid is not None): date = grib_get(gid, 'date') - fdate= datetime.datetime(date/10000,mod(date,10000)/100,mod(date,100)) + fdate = datetime.datetime(date/10000, mod(date,10000)/100, + mod(date,100)) gtime = grib_get(gid, 'time') step = grib_get(gid, 'step') - fdatetime=fdate+datetime.timedelta(hours=gtime/100) -# if fdatetime<start or fdatetime>end: -# break + fdatetime = fdate + datetime.timedelta(hours=gtime/100) gtype = grib_get(gid, 'type') paramId = grib_get(gid, 'paramId') parameterName = grib_get(gid, 'parameterName') - level=grib_get(gid,'level') - if step>=c.start_step and step <=c.end_step and fdatetime>=start and fdatetime<=end and paramId in c.paramIds and level in c.levels: - key='{:0>3}_{:0>3}'.format(paramId,level) + level = grib_get(gid, 'level') + if step >= c.start_step and step <= c.end_step and \ + fdatetime >= start and fdatetime <= end and \ + paramId in c.paramIds and level in c.levels: + key = '{:0>3}_{:0>3}'.format(paramId, level) print key - fdatetimestep=fdatetime+datetime.timedelta(hours=step) - if len(fstamp)==0: + fdatetimestep = fdatetime + datetime.timedelta(hours=step) + if len(fstamp) == 0: fstamp[key].append(fdatetimestamp) - fmeta[key].append((paramId,parameterName,gtype,fdatetime,gtime,step,level)) - fdict[key].append(flipud(reshape(grib_get_values(gid),[gdict['Nj'],gdict['Ni']]))) + fmeta[key].append((paramId, parameterName, gtype, + fdatetime, gtime, step, level)) + fdict[key].append(flipud(reshape( + grib_get_values(gid), [gdict['Nj'], gdict['Ni']]))) else: - i=0 - inserted=False + i = 0 + inserted = False for i in range(len(fstamp[key])): - if fdatetimestep<fstamp[key][i]: - fstamp[key][i:i]=[fdatetimestep] - fmeta[key][i:i]=[(paramId,parameterName,gtype,fdatetime,gtime,step,level)] - fdict[key][i:i]=[flipud(reshape(grib_get_values(gid),[gdict['Nj'],gdict['Ni']]))] - inserted=True + if fdatetimestep < fstamp[key][i]: + fstamp[key][i:i] = [fdatetimestep] + fmeta[key][i:i] = [(paramId, parameterName, gtype, + fdatetime, gtime, step, level)] + fdict[key][i:i] = [flipud(reshape( + grib_get_values(gid), + [gdict['Nj'], gdict['Ni']]))] + inserted = True break if not inserted: fstamp[key].append(fdatetimestep) - fmeta[key].append((paramId,parameterName,gtype,fdatetime,gtime,step,level)) - fdict[key].append(flipud(reshape(grib_get_values(gid),[gdict['Nj'],gdict['Ni']]))) - + fmeta[key].append((paramId, parameterName, gtype, + fdatetime, gtime, step, level)) + fdict[key].append(flipud(reshape( + grib_get_values(gid), [gdict['Nj'], gdict['Ni']]))) grib_release(gid) gid = grib_new_from_index(iid) for k in fdict.keys(): - fml=fmeta[k] - fdl=fdict[k] + fml = fmeta[k] + fdl = fdict[k] - for fd,fm in zip(fdl,fml): - ftitle=fm[1]+' {} '.format(fm[-1])+datetime.datetime.strftime(fm[3],'%Y%m%d%H')+' '+stats(fd) - pname='_'.join(fm[1].split())+'_{}_'.format(fm[-1])+datetime.datetime.strftime(fm[3],'%Y%m%d%H')+'.{:0>3}'.format(fm[5]) - plotmap(fd, fm,gdict,ftitle,pname+'.eps') + for fd, fm in zip(fdl, fml): + ftitle = fm[1] + ' {} '.format(fm[-1]) + \ + datetime.datetime.strftime(fm[3], '%Y%m%d%H') #+ ' ' + stats(fd) + pname = '_'.join(fm[1].split()) + '_{}_'.format(fm[-1]) + \ + datetime.datetime.strftime(fm[3], '%Y%m%d%H') + \ + '.{:0>3}'.format(fm[5]) + plotmap(fd, fm, gdict, ftitle, pname + '.eps') for k in fdict.keys(): - fml=fmeta[k] - fdl=fdict[k] - fsl=fstamp[k] + fml = fmeta[k] + fdl = fdict[k] + fsl = fstamp[k] if fdl: - fm=fml[0] - fd=fdl[0] - ftitle=fm[1]+' {} '.format(fm[-1])+datetime.datetime.strftime(fm[3],'%Y%m%d%H')+' '+stats(fd) - pname='_'.join(fm[1].split())+'_{}_'.format(fm[-1])+datetime.datetime.strftime(fm[3],'%Y%m%d%H')+'.{:0>3}'.format(fm[5]) - lat=-20 - lon=20 - plottimeseries(fdl,fml,fsl,lat,lon, gdict, ftitle, pname+'.eps') - -def plottimeseries(flist,fmetalist,ftimestamps,lat,lon,gdict,ftitle,filename): - t1=time.time() - latindex=(lat+90)*180/(gdict['Nj']-1) - lonindex=(lon+179)*360/gdict['Ni'] - farr=asarray(flist) - ts=farr[:,latindex,lonindex] - f=plt.figure(figsize=(12,6.7)) - plt.plot(ftimestamps,ts) + fm = fml[0] + fd = fdl[0] + ftitle = fm[1] + ' {} '.format(fm[-1]) + \ + datetime.datetime.strftime(fm[3], '%Y%m%d%H') #+ ' ' + stats(fd) + pname = '_'.join(fm[1].split()) + '_{}_'.format(fm[-1]) + \ + datetime.datetime.strftime(fm[3], '%Y%m%d%H') + \ + '.{:0>3}'.format(fm[5]) + lat = -20 + lon = 20 + plottimeseries(fdl, fml, fsl, lat, lon, + gdict, ftitle, pname + '.eps') + + return + +def plottimeseries(flist, fmetalist, ftimestamps, lat, lon, + gdict, ftitle, filename): + ''' + @Description: + + @Input: + flist: + The actual data values to be plotted from the grib messages. + + fmetalist: list of strings + Contains some meta date for the data field to be plotted: + parameter id, parameter Name, grid type, date and time, + time, forecast step, level + + ftimestamps: list of datetime + Contains the time stamps in a datetime format, e.g. + + lat: + + lon: + + gdict: + + ftitle: string + The title of the timeseries. + + filename: string + The time series is stored in a file with this name. + + @Return: + <nothing> + ''' + t1 = time.time() + latindex = (lat + 90) * 180 / (gdict['Nj'] - 1) + lonindex = (lon + 179) * 360 / gdict['Ni'] + farr = asarray(flist) + ts = farr[:, latindex, lonindex] + f = plt.figure(figsize=(12,6.7)) + plt.plot(ftimestamps, ts) plt.title(ftitle) - savefig(c.outputdir+'/'+filename) - print 'created ',c.outputdir+'/'+filename + savefig(c.outputdir + '/' + filename) + print 'created ', c.outputdir + '/' + filename plt.close(f) - print time.time()-t1,'s' - -def plotmap(flist,fmetalist,gdict,ftitle,filename): - t1=time.time() - f=plt.figure(figsize=(12,6.7)) + print time.time() - t1, 's' + + return + +def plotmap(flist, fmetalist, gdict, ftitle, filename): + ''' + @Description: + + @Input: + flist + fmetalist + gdict + ftitle + filename + + @Return: + <nothing> + ''' + t1 = time.time() + f = plt.figure(figsize=(12, 6.7)) mbaxes = f.add_axes([0.05, 0.15, 0.8, 0.7]) - m =Basemap(llcrnrlon=-180.,llcrnrlat=-90.,urcrnrlon=180,urcrnrlat=90.) + m = Basemap(llcrnrlon=-180., llcrnrlat=-90., urcrnrlon=180, urcrnrlat=90.) #if bw==0 : #fill_color=rgb(0.6,0.8,1) #else: #fill_color=rgb(0.85,0.85,0.85) - lw=0.3 + lw = 0.3 m.drawmapboundary() - parallels = arange(-90.,91,90.) - # labels = [left,right,top,bottom] - m.drawparallels(parallels,labels=[True,True,True,True],linewidth=lw) - meridians = arange(-180.,181.,60.) - m.drawmeridians(meridians,labels=[True,True,True,True],linewidth=lw) + parallels = arange(-90., 91, 90.) + # labels = [left, right, top, bottom] + m.drawparallels(parallels, labels=[True, True, True, True], linewidth=lw) + meridians = arange(-180., 181., 60.) + m.drawmeridians(meridians, labels=[True, True, True, True], linewidth=lw) m.drawcoastlines(linewidth=lw) - xleft=gdict['longitudeOfFirstGridPointInDegrees'] - if xleft>180.0: - xleft-=360. - x=linspace(xleft,gdict['longitudeOfLastGridPointInDegrees'],gdict['Ni']) - y=linspace(gdict['latitudeOfLastGridPointInDegrees'],gdict['latitudeOfFirstGridPointInDegrees'],gdict['Nj']) - xx, yy = m(*meshgrid(x,y)) - - s=m.contourf(xx,yy, flist) - title(ftitle,y=1.1) + xleft = gdict['longitudeOfFirstGridPointInDegrees'] + if xleft > 180.0: + xleft -= 360. + x = linspace(xleft, gdict['longitudeOfLastGridPointInDegrees'], gdict['Ni']) + y = linspace(gdict['latitudeOfLastGridPointInDegrees'], + gdict['latitudeOfFirstGridPointInDegrees'], gdict['Nj']) + xx, yy = m(*meshgrid(x, y)) + + s = m.contourf(xx, yy, flist) + title(ftitle, y=1.1) cbaxes = f.add_axes([0.9, 0.2, 0.04, 0.6]) - cb=colorbar(cax=cbaxes) + cb = colorbar(cax=cbaxes) - savefig(c.outputdir+'/'+filename) - print 'created ',c.outputdir+'/'+filename + savefig(c.outputdir + '/' + filename) + print 'created ', c.outputdir + '/' + filename plt.close(f) - print time.time()-t1,'s' - - -def interpret_plotargs(*args,**kwargs): - - parser = ArgumentParser(description='Retrieve FLEXPART input from ECMWF MARS archive', + print time.time() - t1, 's' + + return + +def interpret_plotargs(): + ''' + @Description: + Assigns the command line arguments and reads CONTROL file + content. Apply default values for non mentioned arguments. + + @Input: + <nothing> + + @Return: + args: instance of ArgumentParser + Contains the commandline arguments from script/program call. + + c: instance of class ControlFile + Contains all necessary information of a CONTROL file. The parameters + are: DAY1, DAY2, DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS, STREAM, + NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, + RESOL, GAUSS, ACCURACY, OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, + SMOOTH, FORMAT, ADDPAR, WRF, CWC, PREFIX, ECSTORAGE, ECTRANS, + ECFSDIR, MAILOPS, MAILFAIL, GRIB2FLEXPART, DEBUG, INPUTDIR, + OUTPUTDIR, FLEXPART_ROOT_SCRIPTS + For more information about format and content of the parameter see + documentation. + ''' + parser = ArgumentParser(description='Retrieve FLEXPART input from ' + \ + 'ECMWF MARS archive', formatter_class=ArgumentDefaultsHelpFormatter) # the most important arguments @@ -211,68 +375,74 @@ def interpret_plotargs(*args,**kwargs): parser.add_argument( "--end_step", dest="end_step", help="end_step in hours") -# some arguments that override the default in the control file - parser.add_argument("--levelist", dest="levelist",help="Vertical levels to be retrieved, e.g. 30/to/60") - parser.add_argument("--area", dest="area", help="area defined as north/west/south/east") - parser.add_argument("--paramIds", dest="paramIds", help="parameter IDs") - parser.add_argument("--prefix", dest="prefix",default='EN', help="output file name prefix") +# some arguments that override the default in the CONTROL file + parser.add_argument("--levelist", dest="levelist", + help="Vertical levels to be retrieved, e.g. 30/to/60") + parser.add_argument("--area", dest="area", + help="area defined as north/west/south/east") + parser.add_argument("--paramIds", dest="paramIds", + help="parameter IDs") + parser.add_argument("--prefix", dest="prefix", default='EN', + help="output file name prefix") # set the working directories - parser.add_argument("--inputdir", dest="inputdir",default=None, + parser.add_argument("--inputdir", dest="inputdir", default=None, help="root directory for storing intermediate files") - parser.add_argument("--outputdir", dest="outputdir",default=None, + parser.add_argument("--outputdir", dest="outputdir", default=None, help="root directory for storing output files") parser.add_argument("--flexpart_root_scripts", dest="flexpart_root_scripts", - help="FLEXPART root directory (to find grib2flexpart and COMMAND file)\n\ - Normally ECMWFDATA resides in the scripts directory of the FLEXPART distribution") + help="FLEXPART root directory (to find \ + 'grib2flexpart and COMMAND file)\n \ + Normally ECMWFDATA resides in the scripts directory \ + of the FLEXPART distribution") - parser.add_argument("--controlfile", dest="controlfile",default='CONTROL.temp', - help="file with control parameters") + parser.add_argument("--controlfile", dest="controlfile", + default='CONTROL.temp', help="file with CONTROL parameters") args = parser.parse_args() try: - c=Control(args.controlfile) + c = ControlFile(args.controlfile) except IOError: try: - c=Control(localpythonpath+args.controlfile) + c = ControlFile(localpythonpath + args.controlfile) except: - print 'Could not read control file "'+args.controlfile+'"' + print 'Could not read CONTROL file "' + args.controlfile + '"' print 'Either it does not exist or its syntax is wrong.' - print 'Try "'+sys.argv[0].split('/')[-1]+' -h" to print usage information' + print 'Try "' + sys.argv[0].split('/')[-1] + \ + ' -h" to print usage information' exit(1) if args.levelist: - c.levels=args.levelist.split('/') + c.levels = args.levelist.split('/') else: - c.levels=[0] + c.levels = [0] if args.area: - c.area=args.area.split('/') + c.area = args.area.split('/') else: - c.area='[0,0]' + c.area = '[0,0]' - c.paramIds=args.paramIds.split('/') + c.paramIds = args.paramIds.split('/') if args.start_step: - c.start_step=int(args.start_step) + c.start_step = int(args.start_step) else: - c.start_step=0 + c.start_step = 0 if args.end_step: - c.end_step=int(args.end_step) + c.end_step = int(args.end_step) else: - c.end_step=0 + c.end_step = 0 - c.start_date=args.start_date - c.end_date=args.end_date - c.prefix=args.prefix - c.inputdir=args.inputdir + c.start_date = args.start_date + c.end_date = args.end_date + c.prefix = args.prefix + c.inputdir = args.inputdir if args.outputdir: - c.outputdir=args.outputdir + c.outputdir = args.outputdir else: - c.outputdir=c.inputdir + c.outputdir = c.inputdir - return args,c + return args, c if __name__ == "__main__": + main() - args,c=interpret_plotargs() - plot_retrieved(args,c) diff --git a/python/prepareFLEXPART.py b/python/prepareFLEXPART.py index 9c5ccbe..b1e0b86 100755 --- a/python/prepareFLEXPART.py +++ b/python/prepareFLEXPART.py @@ -2,72 +2,70 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -#AP -# - Change History ist nicht angepasst ans File! Original geben lassen # - wieso cleanup in main wenn es in prepareflexpart bereits abgefragt wurde? # doppelt gemoppelt? # - wieso start=startm1 wenn basetime = 0 ? wenn die fluxes nicht mehr # relevant sind? verstehe ich nicht #************************************************************************ -""" -@Author: Anne Fouilloux (University of Oslo) - -@Date: October 2014 - -@ChangeHistory: - November 2015 - Leopold Haimberger (University of Vienna): - - using the WebAPI also for general MARS retrievals - - job submission on ecgate and cca - - job templates suitable for twice daily operational dissemination - - dividing retrievals of longer periods into digestable chunks - - retrieve also longer term forecasts, not only analyses and - short term forecast data - - conversion into GRIB2 - - conversion into .fp format for faster execution of FLEXPART - - February 2018 - Anne Philipp (University of Vienna): - - applied PEP8 style guide - - added documentation - - minor changes in programming style for consistence - - BUG: removed call of cleanup-Function after call of prepareFlexpart - since it is already called in prepareFlexpart at the end! - -@License: - (C) Copyright 2014-2018. - - 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. - -@Requirements: - - A standard python 2.6 or 2.7 installation - - dateutils - - ECMWF specific packages, all available from https://software.ecmwf.int/ - ECMWF WebMARS, gribAPI with python enabled, emoslib and - ecaccess web toolkit - -@Description: - Further documentation may be obtained from www.flexpart.eu. - - Functionality provided: - Prepare input 3D-wind fields in hybrid coordinates + - surface fields for FLEXPART runs -""" +#******************************************************************************* +# @Author: Anne Fouilloux (University of Oslo) +# +# @Date: October 2014 +# +# @Change History: +# +# November 2015 - Leopold Haimberger (University of Vienna): +# - using the WebAPI also for general MARS retrievals +# - job submission on ecgate and cca +# - job templates suitable for twice daily operational dissemination +# - dividing retrievals of longer periods into digestable chunks +# - retrieve also longer term forecasts, not only analyses and +# short term forecast data +# - conversion into GRIB2 +# - conversion into .fp format for faster execution of FLEXPART +# +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation +# - minor changes in programming style for consistence +# - BUG: removed call of cleanup-Function after call of +# prepareFlexpart in main since it is already called in +# prepareFlexpart at the end! +# - created function main and moved the two function calls for +# arguments and prepareFLEXPART into it +# +# @License: +# (C) Copyright 2014-2018. +# +# 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. +# +# @Program Functionality: +# This program prepares the final version of the grib files which are +# then used by FLEXPART. It converts the bunch of grib files extracted +# via getMARSdata by doing for example the necessary conversion to get +# consistent grids or the disaggregation of flux data. Finally, the +# program combines the data fields in files per available hour with the +# naming convention xxYYMMDDHH, where xx should be 2 arbitrary letters +# (mostly xx is chosen to be "EN"). +# +# @Program Content: +# - main +# - prepareFLEXPART +# +#******************************************************************************* + # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ -import calendar import shutil import datetime -import time +#import time import os import inspect import sys import socket from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter -import UIOFiles -import Control -import Tools -import ECFlexpart hostname = socket.gethostname() ecapi = 'ecmwf' not in hostname @@ -77,24 +75,52 @@ try: except ImportError: ecapi = False -# add path to submit.py to pythonpath so that python finds its buddies -localpythonpath=os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) +# add path to pythonpath so that python finds its buddies +localpythonpath = os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) if localpythonpath not in sys.path: sys.path.append(localpythonpath) + +# software specific classes and modules from flex_extract +from UIOFiles import UIOFiles +from Tools import interpret_args_and_control, cleanup +from ECFlexpart import ECFlexpart # ------------------------------------------------------------------------------ # FUNCTION # ------------------------------------------------------------------------------ -def prepareFLEXPART(args, c): +def main(): ''' @Description: + If prepareFLEXPART is called from command line, this function controls + the program flow and calls the argumentparser function and + the prepareFLEXPART function for preparation of GRIB data for FLEXPART. + + @Input: + <nothing> + @Return: + <nothing> + ''' + args, c = interpret_args_and_control() + prepareFLEXPART(args, c) + + return + +def prepareFLEXPART(args, c): + ''' + @Description: + Lists all grib files retrieved from MARS with getMARSdata and + uses prepares data for the use in FLEXPART. Specific data fields + are converted to a different grid and the flux data are going to be + disaggregated. The data fields are collected by hour and stored in + a file with a specific FLEXPART relevant naming convention. @Input: args: instance of ArgumentParser Contains the commandline arguments from script/program call. - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL file, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -133,17 +159,15 @@ def prepareFLEXPART(args, c): endp1 = end + datetime.timedelta(days=1) # get all files with flux data to be deaccumulated - inputfiles = UIOFiles.UIOFiles(['.grib', '.grb', '.grib1', - '.grib2', '.grb1', '.grb2']) - - inputfiles.listFiles(c.inputdir, '*OG_acc_SL*.' + c.ppid + '.*') + inputfiles = UIOFiles('*OG_acc_SL*.' + c.ppid + '.*') + inputfiles.listFiles(c.inputdir) # create output dir if necessary if not os.path.exists(c.outputdir): os.makedirs(c.outputdir) # deaccumulate the flux data - flexpart = ECFlexpart.ECFlexpart(c, fluxes=True) + flexpart = ECFlexpart(c, fluxes=True) flexpart.write_namelist(c, 'fort.4') flexpart.deacc_fluxes(inputfiles, c) @@ -151,10 +175,8 @@ def prepareFLEXPART(args, c): "/to/" + end.strftime("%Y%m%d")) # get a list of all files from the root inputdir - inputfiles = UIOFiles.UIOFiles(['.grib', '.grb', '.grib1', - '.grib2', '.grb1', '.grb2']) - - inputfiles.listFiles(c.inputdir, '????__??.*' + c.ppid + '.*') + inputfiles = UIOFiles('????__??.*' + c.ppid + '.*') + inputfiles.listFiles(c.inputdir) # produce FLEXPART-ready GRIB files and # process GRIB files - @@ -162,7 +184,7 @@ def prepareFLEXPART(args, c): if c.basetime == '00': start = startm1 - flexpart = ECFlexpart.ECFlexpart(c, fluxes=False) + flexpart = ECFlexpart(c, fluxes=False) flexpart.create(inputfiles, c) flexpart.process_output(c) @@ -171,10 +193,9 @@ def prepareFLEXPART(args, c): if int(c.debug) != 0: print('Temporary files left intact') else: - Tools.cleanup(c) + cleanup(c) return if __name__ == "__main__": - args, c = Tools.interpret_args_and_control() - prepareFLEXPART(args, c) + main() diff --git a/python/profiling.py b/python/profiling.py index c03d35d..b20e6e6 100644 --- a/python/profiling.py +++ b/python/profiling.py @@ -2,26 +2,29 @@ # -*- coding: utf-8 -*- #************************************************************************ # TODO AP -# - add description of file # - check of license of book content #************************************************************************ -""" -@Author: Anne Philipp (University of Vienna) +#******************************************************************************* +# +# @Author: Anne Philipp (University of Vienna) +# +# @Date: March 2018 +# +# @License: +# (C) Copyright 2018. +# +# 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. +# +# @Program functionality: +# This module is not part of flex_extract. It is just used for testing and +# performance analysis of some functions. +# +# @Program Content: +# - timefn +# +#******************************************************************************* -@Date: March 2018 - -@License: - (C) Copyright 2018. - - 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. - -@Requirements: - A standard python 2.6 or 2.7 installation - -@Description: - ... -""" # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ diff --git a/python/submit.py b/python/submit.py index 3c4030c..0685103 100755 --- a/python/submit.py +++ b/python/submit.py @@ -6,39 +6,46 @@ # - Change History ist nicht angepasst ans File! Original geben lassen # - dead code ? what to do? # - seperate operational and reanlysis for clarification -# - add correct file description # - divide in two submits , ondemand und operational # - #************************************************************************ -""" -@Author: Anne Fouilloux (University of Oslo) - -@Date: October 2014 - -@ChangeHistory: - November 2015 - Leopold Haimberger (University of Vienna): - - job submission on ecgate and cca - - job templates suitable for twice daily operational dissemination - - February 2018 - Anne Philipp (University of Vienna): - - applied PEP8 style guide - - added documentation - - minor changes in programming style (for consistence) - -@License: - (C) Copyright 2014-2018. - - 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. - -@Requirements: - - A standard python 2.6 or 2.7 installation - -@Description: - Further documentation may be obtained from www.flexpart.eu. +#******************************************************************************* +# @Author: Anne Fouilloux (University of Oslo) +# +# @Date: October 2014 +# +# @Change History: +# +# November 2015 - Leopold Haimberger (University of Vienna): +# - job submission on ecgate and cca +# - job templates suitable for twice daily operational dissemination +# +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation +# - minor changes in programming style (for consistence) +# +# @License: +# (C) Copyright 2014-2018. +# +# 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. +# +# @Program Functionality: +# This program is the main program of flex_extract and controls the +# program flow. +# If it is supposed to work locally then it works through the necessary +# functions getMARSdata and prepareFlexpart. Otherwise it prepares +# a shell job script which will do the necessary work on the +# ECMWF server and is submitted via ecaccess-job-submit. +# +# @Program Content: +# - main +# - submit +# +#******************************************************************************* -""" # ------------------------------------------------------------------------------ # MODULES # ------------------------------------------------------------------------------ @@ -47,21 +54,25 @@ import sys import glob import subprocess import inspect -# add the pythondir path so that python finds its buddies (from flex_extract) -localpythonpath = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) -sys.path.append(localpythonpath) +# add path to pythonpath so that python finds its buddies +localpythonpath = os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) +if localpythonpath not in sys.path: + sys.path.append(localpythonpath) # software specific classes and modules from flex_extract from Tools import interpret_args_and_control, normalexit from getMARSdata import getMARSdata from prepareFLEXPART import prepareFLEXPART + # ------------------------------------------------------------------------------ # FUNCTIONS # ------------------------------------------------------------------------------ + def main(): ''' @Description: - Get the arguments from script call and from Control file. + Get the arguments from script call and from CONTROL file. Decides from the argument "queue" if the local version is done "queue=None" or the gateway version with "queue=ecgate" or "queue=cca". @@ -101,8 +112,8 @@ def submit(jtemplate, c, queue): the job call and mail report instructions. Default is "job.temp". - c: instance of class Control - Contains all the parameters of control files, which are e.g.: + c: instance of class ControlFile + Contains all the parameters of CONTROL file, which are e.g.: DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME, STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT, LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY, @@ -126,7 +137,7 @@ def submit(jtemplate, c, queue): lftext = f.read().split('\n') insert_point = lftext.index('EOF') - # put all parameters of control instance into a list + # put all parameters of ControlFile instance into a list clist = c.tolist() colist = [] # operational mt = 0 diff --git a/python/testsuite.py b/python/testsuite.py index 217a4e5..199fa89 100755 --- a/python/testsuite.py +++ b/python/testsuite.py @@ -1,28 +1,51 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- +#************************************************************************ +# TODO AP +# +# - provide more tests +# - provide more documentation +# - +#************************************************************************ -# 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. +#******************************************************************************* +# @Author: Leopold Haimberger (University of Vienna) +# +# @Date: December 2015 +# +# @Change History: +# +# February 2018 - Anne Philipp (University of Vienna): +# - applied PEP8 style guide +# - added documentation # -# Leopold Haimberger, Dec 2015 +# @License: +# (C) Copyright 2015-2018. # -# Functionality provided: This script triggers the ECMWFDATA test suite. Call with -# testsuite.py [test group] +# 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. # +# @Program Functionality: +# This script triggers the ECMWFDATA test suite. Call with +# testsuite.py [test group] # -# Further documentation may be obtained from www.flexpart.eu +# @Program Content: # -# Test groups are specified in testsuite.json -# in addition to a standard python 2.6 or 2.7 installation the following packages need to be installed -# ECMWF WebMARS, gribAPI with python enabled, emoslib, ecaccess web toolkit, all available from https://software.ecmwf.int/ -# dateutils -# matplotlib (optional, for debugging) +#******************************************************************************* -import os,sys +# ------------------------------------------------------------------------------ +# MODULES +# ------------------------------------------------------------------------------ +import os +import sys import json import subprocess +# ------------------------------------------------------------------------------ +# PROGRAM +# ------------------------------------------------------------------------------ try: - taskfile=open('testsuite.json') + taskfile = open('testsuite.json') except: print 'could not open suite definition file testsuite.json' exit() @@ -32,59 +55,60 @@ if not os.path.isfile('../src/CONVERT2'): print 'please run "install.py --target=local" first' exit() -fprs=os.getenv('FLEXPART_ROOT_SCRIPTS') +fprs = os.getenv('FLEXPART_ROOT_SCRIPTS') if fprs is None: print 'FLEXPART_ROOT_SCRIPTS not set .. some test jobs may fail' -tasks=json.load(taskfile,encoding='latin-1') +tasks = json.load(taskfile, encoding='latin-1') taskfile.close() if not os.path.exists('../test'): os.makedirs('../test') -if len(sys.argv)>1: - groups=sys.argv[1:] +if len(sys.argv) > 1: + groups = sys.argv[1:] else: - groups=['xinstall','default','ops','work','cv','fc']#,'hires'] -jobcounter=0 -jobfailed=0 + groups = ['xinstall', 'default', 'ops', 'work', 'cv', 'fc']#,'hires'] +jobcounter = 0 +jobfailed = 0 for g in groups: try: - tk,tv=g,tasks[g] + tk, tv = g, tasks[g] except: continue - garglist=[] - for ttk,ttv in tv.iteritems(): - if isinstance(ttv,basestring): - if ttk!='script': - garglist.append('--'+ttk) - if '$'==ttv[0]: + garglist = [] + for ttk, ttv in tv.iteritems(): + if isinstance(ttv, basestring): + if ttk != 'script': + garglist.append('--' + ttk) + if '$' == ttv[0]: garglist.append(os.path.expandvars(ttv)) else: garglist.append(ttv) - for ttk,ttv in tv.iteritems(): - if isinstance(ttv,dict): - arglist=[] - for tttk,tttv in ttv.iteritems(): - if isinstance(tttv,basestring): - arglist.append('--'+tttk) + for ttk, ttv in tv.iteritems(): + if isinstance(ttv, dict): + arglist = [] + for tttk, tttv in ttv.iteritems(): + if isinstance(tttv, basestring): + arglist.append('--' + tttk) if '$' in tttv[0]: arglist.append(os.path.expandvars(tttv)) else: arglist.append(tttv) - print 'Command: ',' '.join([tv['script']]+garglist+arglist) - o='../test/'+tk+'_'+ttk+'_'+'_'.join(ttv.keys()) - print 'Output will be sent to ',o - f=open(o,'w') + print 'Command: ', ' '.join([tv['script']] + garglist + arglist) + o = '../test/' + tk + '_' + ttk + '_' + '_'.join(ttv.keys()) + print 'Output will be sent to ', o + f = open(o, 'w') try: - p=subprocess.check_call([tv['script']]+garglist+arglist,stdout=f,stderr=f) + p = subprocess.check_call([tv['script']] + garglist + arglist, + stdout=f, stderr=f) except: f.write('\nFAILED\n') print 'FAILED' - jobfailed+=1 - jobcounter+=1 + jobfailed += 1 + jobcounter += 1 f.close() print 'Test suite tasks completed' -print str(jobcounter-jobfailed)+' successful, '+str(jobfailed)+' failed' +print str(jobcounter-jobfailed) + ' successful, ' + str(jobfailed) + ' failed' print 'If tasks have been submitted via ECACCESS please check emails' #print tasks diff --git a/src/Makefile.local.gfortran b/src/Makefile.local.gfortran index e7a060b..3847d57 100644 --- a/src/Makefile.local.gfortran +++ b/src/Makefile.local.gfortran @@ -11,8 +11,8 @@ .s .s~ .sh .sh~ .h .h~ .C .C~ .a -GRIB_API_INCLUDE_DIR=/usr/local/gcc-4.9.3/grib1.12.3//include -GRIB_API_LIB= -L/usr/local/gcc-4.9.3/grib1.12.3/lib -Bstatic -lgrib_api_f77 -lgrib_api_f90 -lgrib_api -Bdynamic -lm -ljasper +GRIB_API_INCLUDE_DIR=/usr/local/gcc-4.9.3/grib_api-1.14.3//include +GRIB_API_LIB= -L/usr/local/gcc-4.9.3/grib_api-1.14.3/lib -Bstatic -lgrib_api_f77 -lgrib_api_f90 -lgrib_api -Bdynamic -lm -ljasper EMOSLIB=-lemosR64 OPT = -g -O3 -fopenmp diff --git a/src/Makefile.local.ifort b/src/Makefile.local.ifort index ef6a78b..6f58a35 100644 --- a/src/Makefile.local.ifort +++ b/src/Makefile.local.ifort @@ -11,15 +11,15 @@ .s .s~ .sh .sh~ .h .h~ .C .C~ .a -GRIB_API_INCLUDE_DIR=/usr/local/ifort/grib1.12.3//include -GRIB_API_LIB=-openmp -L/usr/local/ifort/grib1.12.3//lib -Bstatic -lgrib_api_f77 -lgrib_api_f90 -lgrib_api -Bdynamic -lm -ljasper #-lopenjpeg +GRIB_API_INCLUDE_DIR=/home/srvx1/tmc/TestEnv/Libraries/eccodes-2.6.0_ifort/include +GRIB_API_LIB= -L/home/srvx1/tmc/TestEnv/Libraries/eccodes-2.6.0_ifort/lib -Bstatic -leccodes_f90 -leccodes -Bdynamic -lm -ljasper -OPT = -g +OPT = -g -O3 -mcmodel=medium -unroll -inline -heap-arrays 32 DEBUG = -g LIB = $(GRIB_API_LIB) -lemosR64 -lgfortran -FC=ifort -132 -traceback -r8 -F90C=ifort -132 -traceback -r8 +FC=/opt/intel/bin/ifort -132 -traceback -r8 +F90C=/opt/intel/bin/ifort -132 -traceback -r8 FFLAGS = $(OPT) -I. -I$(GRIB_API_INCLUDE_DIR) F90FLAGS = $(OPT) -I. -I$(GRIB_API_INCLUDE_DIR) @@ -42,13 +42,13 @@ clean: rm *.o phgrreal.o: phgrreal.f - $(F90C) -c -g -O3 -fopenmp phgrreal.f + $(F90C) -c -g -O3 phgrreal.f grphreal.o: grphreal.f - $(F90C) -c -g -O3 -fopenmp grphreal.f + $(F90C) -c -g -O3 grphreal.f ftrafo.o: ftrafo.f - $(F90C) -c -g -O3 -fopenmp ftrafo.f + $(F90C) -c -g -O3 ftrafo.f $(BINDIR)/CONVERT2: phgrreal.o grphreal.o ftrafo.o rwGRIB2.o posnam.o preconvert.o $(F90C) $(DEBUG) $(OPT) -o $(BINDIR)/CONVERT2 ftrafo.o phgrreal.o grphreal.o rwGRIB2.o posnam.o preconvert.o ${LIB} -- GitLab