diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7aafb3cecc62724615f0342789ad819faef1a36c --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# +# gitignore lists all directories and files which should not be versionized!!! +# + +# these dirs and files shouldn't be versionized at any time in this project!!! +*.pyc +*.o +*.mod +*.tar* +/work* +/src/CONVERT2 +/python/ECMWF_ENV + + +# temporary ignorance of the following files! +Content_Paper2.txt +DOCUFPEXTRACT.txt +FLEXEXTRACT_CONTENT.txt +NewDocu.odt +dokuECMWFDATA + +/python/.cache/ +/python/CONTROL.temp.backup + +/python/SphinxDoc/ +/python/assoc.txt +/python/classes.dot +/python/classes.dot.png + +/python/packages.dot +/python/packages.dot.png +/python/prepare_flexpart.py.lprof +/python/pylintrc +/python/testecmwfapi.py + +/pythontest/ + diff --git a/doc/.~lock.ErrorDebug.ods# b/doc/.~lock.ErrorDebug.ods# new file mode 100644 index 0000000000000000000000000000000000000000..06cf131f74fd43c201de6200ad39a547aa7a8e78 --- /dev/null +++ b/doc/.~lock.ErrorDebug.ods# @@ -0,0 +1 @@ +,philipa8,localhost,21.08.2018 16:15,file:///home/philipa8/.config/libreoffice/4; \ No newline at end of file diff --git a/doc/ECMWF_FPparameter.xlsx b/doc/ECMWF_FPparameter.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..20207d5a2ffdfa77f4f6686d6fc3332e284e3988 Binary files /dev/null and b/doc/ECMWF_FPparameter.xlsx differ diff --git a/doc/ErrorDebug.ods b/doc/ErrorDebug.ods new file mode 100644 index 0000000000000000000000000000000000000000..11547601ff905a06a7628fc10458e8c94cee0f16 Binary files /dev/null and b/doc/ErrorDebug.ods differ diff --git a/doc/GUIDE.odt b/doc/GUIDE.odt new file mode 100644 index 0000000000000000000000000000000000000000..edfa22d896f30ff66f03c4c980d8d988e8ca0278 Binary files /dev/null and b/doc/GUIDE.odt differ diff --git a/doc/SIP.doc b/doc/SIP.doc new file mode 100644 index 0000000000000000000000000000000000000000..9472fc7fc47a2997d2ec776483fba4b94f1ca981 Binary files /dev/null and b/doc/SIP.doc differ diff --git a/doc/SIP.pdf b/doc/SIP.pdf new file mode 100644 index 0000000000000000000000000000000000000000..71e75f355e5aae9f0d9f127860ec61e1895c290c Binary files /dev/null and b/doc/SIP.pdf differ diff --git a/doc/SUT_ondemand.doc b/doc/SUT_ondemand.doc new file mode 100644 index 0000000000000000000000000000000000000000..e8a7594f3227f34a19c794f5b796b63f9505340a Binary files /dev/null and b/doc/SUT_ondemand.doc differ diff --git a/doc/SUT_ondemand.pdf b/doc/SUT_ondemand.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d2b75f3d69a14a0d9e4952c38b993baa6744dcdc Binary files /dev/null and b/doc/SUT_ondemand.pdf differ diff --git a/mk_upload_tarball.sh b/mk_upload_tarball.sh new file mode 100644 index 0000000000000000000000000000000000000000..4266f64ddf6c27dc57e618d7432fb3b0a47bbde1 --- /dev/null +++ b/mk_upload_tarball.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# +# @Author: Anne Philipp +# +# @Date: June, 7 2018 +# +# @Description: Makes a tarball for uploading on flexpart.eu +# + +tarname='flex_extract_v7.1.tar.gz' + + +tar --exclude='./src/*.o' --exclude='./src/*.mod' --exclude='./src/CONVERT2' --exclude='./python/*.pyc' --exclude='./python/*.ksh' --exclude='./python/ECMWF_ENV' --exclude='./python/*sh' --exclude='*sh' --exclude='*tar.gz' --exclude='work' --exclude='./python/job.temp' -zcvf $tarname . diff --git a/python/CONTROL.temp b/python/CONTROL.temp index c3d097411cfc71a179dc87df88c0407b6f026d7c..b972567b215eeb95a15c4ad666f79263cbdd0e3b 100644 --- a/python/CONTROL.temp +++ b/python/CONTROL.temp @@ -1,4 +1,4 @@ -DAY1 +DAY1 20130501 DAY2 DTIME 3 TYPE AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC diff --git a/python/CONTROL.test b/python/CONTROL.test new file mode 100644 index 0000000000000000000000000000000000000000..6a9ce870554144c1ad88b9a724cdf123f9f9e10b --- /dev/null +++ b/python/CONTROL.test @@ -0,0 +1,18 @@ +DTIME 3 +TYPE AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC +TIME 00 00 00 00 00 00 06 00 00 00 00 00 12 12 12 12 12 12 18 12 12 12 12 12 +STEP 00 01 02 03 04 05 00 07 08 09 10 11 00 01 02 03 04 05 00 07 08 09 10 11 +CLASS EI +STREAM OPER +EXPVER 1 +GRID 5000 +LEFT -15000 +LOWER 30000 +UPPER 75000 +RIGHT 45000 +LEVELIST 55/to/60 +RESOL 63 +GAUSS 1 +ADDPAR 186/187/188/235/139/39 +PREFIX EItest_ +ECTRANS 1 diff --git a/python/CONTROL.worktest b/python/CONTROL.worktest new file mode 100644 index 0000000000000000000000000000000000000000..7bd637d8b4a27f63ffc03eb0ea1b4669661afa85 --- /dev/null +++ b/python/CONTROL.worktest @@ -0,0 +1,37 @@ +DAY1 20100101 +DAY2 +DTIME 3 +TYPE AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC +TIME 00 00 00 00 00 00 06 00 00 00 00 00 12 12 12 12 12 12 18 12 12 12 12 12 +STEP 00 01 02 03 04 05 00 07 08 09 10 11 00 01 02 03 04 05 00 07 08 09 10 11 +CLASS EI +STREAM OPER +NUMBER OFF +EXPVER 1 +GRID 5000 +LEFT -10000 +LOWER 30000 +UPPER 40000 +RIGHT 10000 +LEVEL 60 +LEVELIST 58/to/60 +RESOL 63 +GAUSS 1 +ACCURACY 16 +OMEGA 0 +OMEGADIFF 0 +ETA 0 +ETADIFF 0 +DPDETA 1 +SMOOTH 0 +FORMAT GRIB1 +ADDPAR 186/187/188/235/139/39 +PREFIX EI +ECSTORAGE 0 +ECTRANS 1 +ECFSDIR ectmp:/${USER}/econdemand/ +MAILFAIL ${USER} +MAILOPS ${USER} +GRIB2FLEXPART 0 +EOF + diff --git a/python/CONTROL_EI b/python/CONTROL_EI new file mode 100644 index 0000000000000000000000000000000000000000..c3d097411cfc71a179dc87df88c0407b6f026d7c --- /dev/null +++ b/python/CONTROL_EI @@ -0,0 +1,37 @@ +DAY1 +DAY2 +DTIME 3 +TYPE AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC +TIME 00 00 00 00 00 00 06 00 00 00 00 00 12 12 12 12 12 12 18 12 12 12 12 12 +STEP 00 01 02 03 04 05 00 07 08 09 10 11 00 01 02 03 04 05 00 07 08 09 10 11 +CLASS EI +STREAM OPER +NUMBER OFF +EXPVER 1 +GRID 5000 +LEFT -15000 +LOWER 30000 +UPPER 75000 +RIGHT 45000 +LEVEL 60 +LEVELIST 55/to/60 +RESOL 63 +GAUSS 1 +ACCURACY 16 +OMEGA 0 +OMEGADIFF 0 +ETA 0 +ETADIFF 0 +DPDETA 1 +SMOOTH 0 +FORMAT GRIB1 +ADDPAR 186/187/188/235/139/39 +PREFIX EI +ECSTORAGE 0 +ECTRANS 1 +ECFSDIR ectmp:/${USER}/econdemand/ +MAILFAIL ${USER} +MAILOPS ${USER} +GRIB2FLEXPART 0 +EOF + diff --git a/python/CONTROL_FC b/python/CONTROL_FC index 11efa7254276eca1aa0893a5e29120b59c171936..e05bac201300f65cb974f9aae68c59bd24b311ef 100644 --- a/python/CONTROL_FC +++ b/python/CONTROL_FC @@ -1,9 +1,12 @@ -DAY1 20131107 -DAY2 20131108 -DTIME 1 +DAY1 +DAY2 +DTIME 3 M_TYPE FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC FC M_TIME 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 M_STEP 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 +ACCTYPE FC +ACCTIME 00 +ACCMAXSTEP 36 M_MAXSTEP 36 M_LEVEL 137 M_LEVELIST 136/to/137 @@ -12,27 +15,17 @@ M_STREAM OPER M_NUMBER OFF M_EXPVER 1 M_GRID 1000 -M_LEFT -179000 -M_LOWER -90000 -M_UPPER 90000 -M_RIGHT 180000 +M_LEFT -25000 +M_LOWER 10000 +M_UPPER 60000 +M_RIGHT 60000 M_RESOL 159 -M_GAUSS 0 -M_ACCURACY 16 -M_OMEGA 0 -M_OMEGADIFF 0 M_ETA 1 -M_ETADIFF 0 M_DPDETA 1 M_SMOOTH 0 -M_FORMAT GRIB1 M_ADDPAR /186/187/188/235/139/39 M_WRF 1 M_CWC 1 PREFIX EE -ECSTORAGE 1 -ECTRANS 0 -ECFSDIR ectmp:/${USER}/econdemand/ -MAILOPS ${USER} -MAILFAIL ${USER} +ECTRANS 1 EOF diff --git a/python/CONTROL_PF.temp b/python/CONTROL_PF.temp index f40bdd31acef64a76c023aab0628e3a0683fc768..8eb9cb511a84744878b370f3f9208c93080abc54 100644 --- a/python/CONTROL_PF.temp +++ b/python/CONTROL_PF.temp @@ -33,6 +33,6 @@ ECTRANS 1 ECFSDIR ectmp:/${USER}/econdemand/ MAILOPS ${USER} MAILFAIL ${USER} -FLEXPARTDIR ${HOME}/ECMWFDATA7.0/flexpart_code +FLEXPARTDIR ${HOME}/flex_extract_v7.1/flexpart_code GRIB2FLEXPART grib2flexpart EOF diff --git a/python/ControlFile.py b/python/ControlFile.py index b4bf7121f1e8ce81da076cd9bd4ceefd2dc9cf08..257e072543b77281134b4dcf5123bd1d3dfdb03b 100644 --- a/python/ControlFile.py +++ b/python/ControlFile.py @@ -1,9 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -#************************************************************************ -# ToDo AP -# - write a test class -#************************************************************************ #******************************************************************************* # @Author: Leopold Haimberger (University of Vienna) # @@ -18,6 +14,10 @@ # - changed name of class Control to ControlFile for more # self-explanation naming # - outsource of class ControlFile +# - initialisation of class attributes ( to avoid high number of +# conditional statements and set default values ) +# - divided assignment of attributes and the check of conditions +# - outsourced the commandline argument assignments to control attributes # # @License: # (C) Copyright 2015-2018. @@ -35,29 +35,16 @@ # # @Class Content: # - __init__ +# - __read_controlfile__ # - __str__ +# - assign_args_to_control +# - assign_envs_to_control +# - check_conditions +# - check_install_conditions # - to_list # # @Class Attributes: -# - start_date -# - end_date -# - accuracy -# - omega -# - cwc -# - omegadiff -# - etadiff -# - level -# - levelist -# - step -# - maxstep -# - prefix -# - makefile -# - basetime -# - date_chunk -# - grib2flexpart -# - exedir -# - flexpart_root_scripts -# - ecmwfdatadir +# # #******************************************************************************* @@ -65,6 +52,7 @@ # MODULES # ------------------------------------------------------------------------------ import os +import sys import inspect # ------------------------------------------------------------------------------ @@ -91,9 +79,10 @@ class ControlFile(object): def __init__(self, filename): ''' @Description: - Initialises the instance of ControlFile class and defines and - assign all CONTROL file variables. Set default values if - parameter was not in CONTROL file. + Initialises the instance of ControlFile class and defines + all class attributes with default values. Afterwards calls + function __read_controlfile__ to read parameter from + Control file. @Input: self: instance of ControlFile class @@ -102,17 +91,90 @@ class ControlFile(object): filename: string Name of CONTROL file. + @Return: + <nothing> + ''' + + # list of all possible class attributes and their default values + self.controlfile = filename + self.start_date = None + self.end_date = None + self.date_chunk = 3 + self.dtime = None + self.basetime = None + self.maxstep = None + self.type = None + self.time = None + self.step = None + self.marsclass = None + self.stream = None + self.number = 'OFF' + self.expver = None + self.grid = None + self.area = '' + self.left = None + self.lower = None + self.upper = None + self.right = None + self.level = None + self.levelist = None + self.resol = None + self.gauss = 0 + self.accuracy = 24 + self.omega = 0 + self.omegadiff = 0 + self.eta = 0 + self.etadiff = 0 + self.etapar = 77 + self.dpdeta = 1 + self.smooth = 0 + self.format = 'GRIB1' + self.addpar = None + self.prefix = 'EN' + self.cwc = 0 + self.wrf = 0 + self.ecfsdir = 'ectmp:/${USER}/econdemand/' + self.mailfail = ['${USER}'] + self.mailops = ['${USER}'] + self.grib2flexpart = 0 + self.ecstorage = 0 + self.ectrans = 0 + self.inputdir = '../work' + self.outputdir = self.inputdir + self.ecmwfdatadir = None + self.exedir = None + self.flexpart_root_scripts = None + self.makefile = None + self.destination = None + self.gateway = None + self.ecuid = None + self.ecgid = None + self.install_target = None + self.debug = 0 + + self.__read_controlfile__() + + return + + def __read_controlfile__(self): + ''' + @Description: + Read CONTROL file and assign all CONTROL file variables. + + @Input: + self: instance of ControlFile class + Description see class documentation. + @Return: <nothing> ''' from tools import my_error # read whole CONTROL file - with open(filename) as f: + with open(self.controlfile) 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: @@ -144,9 +206,10 @@ class ControlFile(object): if var is not None: data[1] = data[1][:i] + var + data[1][k+1:] else: - my_error(None, 'Could not find variable ' + - data[1][j+1:k] + ' while reading ' + - filename) + my_error(self.mailfail, + 'Could not find variable ' + + data[1][j+1:k] + ' while reading ' + + self.controlfile) setattr(self, data[0].lower() + '_expanded', data[1]) else: if data[1].lower() != 'none': @@ -158,36 +221,164 @@ class ControlFile(object): 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'): + # script directory + self.ecmwfdatadir = os.path.dirname(os.path.abspath(inspect.getfile( + inspect.currentframe()))) + '/../' + + # Fortran source directory + self.exedir = self.ecmwfdatadir + 'src/' + + return + + def __str__(self): + ''' + @Description: + Prepares a string which have all the ControlFile + class attributes with its associated values. + Each attribute is printed in one line and in + alphabetical order. + + Example: + 'age': 10 + 'color': 'Spotted' + 'kids': 0 + 'legs': 2 + 'name': 'Dog' + 'smell': 'Alot' + + @Input: + self: instance of ControlFile class + Description see class documentation. + + @Return: + string of ControlFile class attributes with their values + ''' + import collections + + attrs = vars(self) + attrs = collections.OrderedDict(sorted(attrs.items())) + + return '\n'.join("%s: %s" % item for item in attrs.items()) + + def assign_args_to_control(self, args): + ''' + @Description: + Overwrites the existing ControlFile instance attributes with + the command line arguments. + + @Input: + self: instance of ControlFile class + Description see class documentation. + + args: instance of ArgumentParser + Contains the commandline arguments from script/program call. + + @Return: + <nothing> + ''' + + # get dictionary of command line parameters and eliminate all + # parameters which are None (were not specified) + args_dict = vars(args) + arguments = {k : args_dict[k] for k in args_dict + if args_dict[k] != None} + + # assign all passed command line arguments to ControlFile instance + for k, v in arguments.iteritems(): + setattr(self, str(k), v) + + return + + def assign_envs_to_control(self, envs): + ''' + @Description: + Assigns the ECMWF environment parameter. + + @Input: + envs: dict of strings + Contains the ECMWF environment parameternames "ECUID", "ECGID", + "DESTINATION" and "GATEWAY" with its corresponding values. + They were read from the file "ECMWF_ENV". + + @Return: + <nothing> + ''' + + for k, v in envs.iteritems(): + setattr(self, str(k).lower(), str(v)) + + return + + def check_conditions(self): + ''' + @Description: + Checks a couple of necessary attributes and conditions, + such as if they exist and contain values. + Otherwise set default values. + + @Input: + self: instance of ControlFile class + Description see class documentation. + + @Return: + <nothing> + ''' + from tools import my_error + import numpy as np + + # check for having at least a starting date + # otherwise program is not allowed to run + if self.start_date is None: + print 'start_date specified neither in command line nor ' + \ + 'in CONTROL file ' + self.controlfile + print 'Try "' + sys.argv[0].split('/')[-1] + \ + ' -h" to print usage information' + sys.exit(1) + + # retrieve just one day if end_date isn't set + if self.end_date is None: 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' + + # assure consistency of levelist and level + if self.levelist is None: + if self.level is None: + print 'Warning: neither levelist nor level ' + \ + 'specified in CONTROL file' + sys.exit(1) else: self.levelist = '1/to/' + self.level else: - if 'to' in self.levelist: + if 'to' in self.levelist.lower(): self.level = self.levelist.split('/')[2] else: self.level = self.levelist.split('/')[-1] - if not hasattr(self, 'maxstep'): - # find out maximum step + # if area was provided at command line + # decompse area into its 4 components + if self.area: + afloat = '.' in self.area + l = self.area.split('/') + if afloat: + for i, item in enumerate(l): + item = str(int(float(item) * 1000)) + self.upper, self.left, self.lower, self.right = l + + # prepare step for correct usage + if '/' in self.step: + l = self.step.split('/') + if 'to' in self.step.lower(): + if 'by' in self.step.lower(): + ilist = np.arange(int(l[0]), int(l[2]) + 1, int(l[4])) + self.step = ['{:0>3}'.format(i) for i in ilist] + else: + my_error(self.mailfail, self.step + ':\n' + + 'if "to" is used, please use "by" as well') + else: + self.step = l + + # if maxstep wasn't provided + # search for it in the "step" parameter + if self.maxstep is None: self.maxstep = 0 for s in self.step: if int(s) > self.maxstep: @@ -195,50 +386,86 @@ class ControlFile(object): 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' + # set root scripts since it is needed later on + if not self.flexpart_root_scripts: + self.flexpart_root_scripts = self.ecmwfdatadir - # script directory - self.ecmwfdatadir = os.path.dirname(os.path.abspath(inspect.getfile( - inspect.currentframe()))) + '/../' - # Fortran source directory - self.exedir = self.ecmwfdatadir + 'src/' + if not isinstance(self.mailfail, list): + if ',' in self.mailfail: + self.mailfail = self.mailfail.split(',') + elif ' ' in self.mailfail: + self.mailfail = self.mailfail.split() + else: + self.mailfail = [self.mailfail] - # FLEXPART directory - if not hasattr(self, 'flexpart_root_scripts'): - self.flexpart_root_scripts = self.ecmwfdatadir + if not isinstance(self.mailops, list): + if ',' in self.mailops: + self.mailops = self.mailops.split(',') + elif ' ' in self.mailops: + self.mailops = self.mailops.split() + else: + self.mailops = [self.mailops] + + if not self.gateway or not self.destination or \ + not self.ecuid or not self.ecgid: + print '\nEnvironment variables GATWAY, DESTINATION, ECUID and ' + \ + 'ECGID were not set properly!' + print 'Please check for excistence of file "ECMWF_ENV" in the ' + \ + 'python directory!' + sys.exit(1) return - def __str__(self): + def check_install_conditions(self): ''' @Description: - Prepares a single string with all the comma seperated ControlFile - class attributes including their values. - - Example: - {'kids': 0, 'name': 'Dog', 'color': 'Spotted', - 'age': 10, 'legs': 2, 'smell': 'Alot'} + Checks a couple of necessary attributes and conditions + for the installation such as if they exist and contain values. + Otherwise set default values. @Input: self: instance of ControlFile class Description see class documentation. @Return: - string of ControlFile class attributes with their values + <nothing> ''' - attrs = vars(self) + if self.install_target and \ + self.install_target not in ['local', 'ecgate', 'cca']: + print 'ERROR: unknown or missing installation target ' + print 'target: ', self.install_target + print 'please specify correct installation target \ + (local | ecgate | cca)' + print 'use -h or --help for help' + sys.exit(1) + + if self.install_target and self.install_target != 'local': + if not self.ecgid or not self.ecuid or \ + not self.gateway or not self.destination: + print 'Please enter your ECMWF user id and group id as well as \ + the \nname of the local gateway and the ectrans \ + destination ' + print 'with command line options --ecuid --ecgid \ + --gateway --destination' + print 'Try "' + sys.argv[0].split('/')[-1] + \ + ' -h" to print usage information' + print 'Please consult ecaccess documentation or ECMWF user \ + support for further details' + sys.exit(1) + + if not self.flexpart_root_scripts: + self.flexpart_root_scripts = '${HOME}' + else: + self.flexpart_root_scripts = self.flexpart_root_scripts + else: + if not self.flexpart_root_scripts: + self.flexpart_root_scripts = '../' - return ', '.join("%s: %s" % item for item in attrs.items()) + if not self.makefile: + self.makefile = 'Makefile.gfortran' + + return def to_list(self): ''' @@ -258,7 +485,10 @@ class ControlFile(object): "ecmwfdatadir" and "flexpart_root_scripts". ''' - attrs = vars(self) + import collections + + attrs = collections.OrderedDict(sorted(vars(self).items())) + l = list() for item in attrs.items(): @@ -282,23 +512,3 @@ class ControlFile(object): return sorted(l) - # def to_dict(self): - # ''' - - # ''' - # parameters_dict = vars(self) - - # # remove unneeded parameter - # parameters_dict.pop('_expanded', None) - # parameters_dict.pop('exedir', None) - # parameters_dict.pop('flexpart_root_scripts', None) - # parameters_dict.pop('ecmwfdatadir', None) - - # parameters_dict_str = {} - # for key, value in parameters_dict.iteritems(): - # if isinstance(value, list): - # parameters_dict_str[str(key)] = get_list_as_string(value, ' ') - # else: - # parameters_dict_str[str(key)] = str(value) - - # return parameters_dict_str diff --git a/python/EcFlexpart.py b/python/EcFlexpart.py index bca2d1cd44dfe5a025ec04bb500e3455868243ce..e030b7a43f9e7066edfb55508a183bf96d777a6b 100644 --- a/python/EcFlexpart.py +++ b/python/EcFlexpart.py @@ -1,15 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -#************************************************************************ -# ToDo 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) # @@ -150,7 +140,7 @@ class EcFlexpart(object): see documentation. fluxes: boolean, optional - Decides if a the flux parameter settings are stored or + Decides if the flux parameter settings are stored or the rest of the parameter list. Default value is False. @@ -174,7 +164,7 @@ class EcFlexpart(object): self.basetime = c.basetime self.dtime = c.dtime i = 0 - if fluxes is True and c.maxstep < 24: + if fluxes and c.maxstep <= 24: # no forecast beyond one day is needed! # Thus, prepare flux data manually as usual # with only forecast fields with start times at 00/12 @@ -191,7 +181,7 @@ class EcFlexpart(object): if c.basetime == '00': btlist = [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0] - if i % int(c.dtime) == 0 and c.maxstep > 24 or i in btlist: + if i % int(c.dtime) == 0 and (i in btlist or c.maxstep > 24): if ty not in self.types.keys(): self.types[ty] = {'times': '', 'steps': ''} @@ -207,7 +197,6 @@ class EcFlexpart(object): self.types[ty]['steps'] += st i += 1 - self.marsclass = c.marsclass self.stream = c.stream self.number = c.number @@ -303,15 +292,15 @@ class EcFlexpart(object): self.params['GG__ML'] = ['U/V/D/77', 'ML', self.glevelist, \ '{}'.format((int(self.resol) + 1) / 2)] - if c.omega == '1': + if hasattr(c, 'omega') and c.omega == '1': self.params['OG__ML'][0] += '/W' # add cloud water content if necessary - if c.cwc == '1': + if hasattr(c, 'cwc') and c.cwc == '1': self.params['OG__ML'][0] += '/CLWC/CIWC' # add vorticity and geopotential height for WRF if necessary - if c.wrf == '1': + if hasattr(c, 'wrf') and c.wrf == '1': self.params['OG__ML'][0] += '/Z/VO' if '/D' not in self.params['OG__ML'][0]: self.params['OG__ML'][0] += '/D' @@ -739,7 +728,7 @@ class EcFlexpart(object): p = subprocess.check_call(['ectrans', '-overwrite', '-gateway', c.gateway, '-remote', c.destination, '-source', ofile]) - print('ectrans:', p) + #print('ectrans:', p) if int(c.ecstorage) == 1 and c.ecapi is False: for ofile in self.outputfilelist: @@ -889,7 +878,7 @@ class EcFlexpart(object): '17':None, '19':None, '21':None, '22':None, '20':None} for prod in product(*index_vals): - # flag for Fortran program CONVERT2, initially False + # flag for Fortran program CONVERT2 and file merging convertFlag = False print 'current prod: ', prod # e.g. prod = ('20170505', '0', '12') @@ -905,10 +894,13 @@ class EcFlexpart(object): # 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 + # Combine all temporary data files into final grib file if + # gid is at least one time not None. Therefore set convertFlag + # to save information. The fortran program CONVERT2 is also + # only done if convertFlag is True convertFlag = True # remove old fort.* files and open new ones + # they are just valid for a single product for k, f in fdict.iteritems(): silent_remove(c.inputdir + "/fort." + k) fdict[k] = open(c.inputdir + '/fort.' + k, 'w') @@ -921,7 +913,6 @@ class EcFlexpart(object): timestamp = datetime.strptime(cdate + '{:0>2}'.format(time/100), '%Y%m%d%H') timestamp += timedelta(hours=int(step)) - cdateH = datetime.strftime(timestamp, '%Y%m%d%H') if c.basetime is not None: @@ -965,7 +956,7 @@ class EcFlexpart(object): gridtype = grib_get(gid, 'gridType') levtype = grib_get(gid, 'typeOfLevel') if paramId == 133 and gridtype == 'reduced_gg': - # Relative humidity (Q.grb) is used as a template only + # Specific humidity (Q.grb) is used as a template only # so we need the first we "meet" with open(c.inputdir + '/fort.18', 'w') as fout: grib_write(gid, fout) @@ -1027,8 +1018,8 @@ class EcFlexpart(object): print 'Parameter 77 (etadot) is missing, most likely it is \ not available for this type or date/time\n' print 'Check parameters CLASS, TYPE, STREAM, START_DATE\n' - my_error(c, 'fort.21 is empty while parameter eta is set \ - to 1 in CONTROL file') + my_error(c.mailfail, '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) @@ -1060,14 +1051,15 @@ class EcFlexpart(object): with open(fnout, 'wb') as fout: for f in flist: - shutil.copyfileobj(open(c.inputdir + '/' + f, 'rb'), fout) + shutil.copyfileobj( + open(c.inputdir + '/' + f, 'rb'), fout) if c.omega == '1': with open(c.outputdir + '/OMEGA', 'wb') as fout: shutil.copyfileobj( open(c.inputdir + '/fort.25', 'rb'), fout) - if c.wrf == '1': + if hasattr(c, 'wrf') and c.wrf == '1': fwrf.close() grib_index_release(iid) @@ -1306,5 +1298,4 @@ class EcFlexpart(object): grib_index_release(iid) - exit() return diff --git a/python/GribTools.py b/python/GribTools.py index c977d4ac28b7142c96adf91eec90808da1515f30..a68d1a5485f1b7e3039081ec7fb176d90466ab06 100644 --- a/python/GribTools.py +++ b/python/GribTools.py @@ -308,7 +308,6 @@ class GribTools(object): if iid is None: iid = grib_index_new_from_file(filename, index_keys) else: - print 'in else zweig' grib_index_add_file(iid, filename) if iid is not None: diff --git a/python/MarsRetrieval.py b/python/MarsRetrieval.py index eccac65c4c38444d29beec73af375d4fe5b07774..54a32b06513e9428f27cf0771b148e2c98e86893 100644 --- a/python/MarsRetrieval.py +++ b/python/MarsRetrieval.py @@ -78,7 +78,7 @@ class MarsRetrieval(object): ''' - def __init__(self, server, marsclass="ei", dtype="", levtype="", + def __init__(self, server, marsclass="ei", type="", levtype="", levelist="", repres="", date="", resol="", stream="", area="", time="", step="", expver="1", number="", accuracy="", grid="", gaussian="", target="", @@ -106,7 +106,7 @@ class MarsRetrieval(object): E4 (ERA40), OD (Operational archive), ea (ERA5). Default is the ERA-Interim dataset "ei". - dtype: string, optional + type: string, optional Determines the type of fields to be retrieved. Selects between observations, images or fields. Examples for fields: Analysis (an), Forecast (fc), @@ -285,7 +285,7 @@ class MarsRetrieval(object): self.server = server self.marsclass = marsclass - self.dtype = dtype + self.type = type self.levtype = levtype self.levelist = levelist self.repres = repres diff --git a/python/UioFiles.py b/python/UioFiles.py index dbb5409cc6112ad98cffdceb4dfea42f8899d037..fe6995320308bfb88805745ac5753ffbdc9dd799 100644 --- a/python/UioFiles.py +++ b/python/UioFiles.py @@ -33,7 +33,8 @@ # # @Class Content: # - __init__ -# - list_files +# - __str__ +# - __list_files__ # - delete_files # # @Class Attributes: @@ -50,7 +51,7 @@ import fnmatch # software specific module from flex_extract #import profiling -from tools import silent_remove +from tools import silent_remove, get_list_as_string # ------------------------------------------------------------------------------ # CLASS @@ -65,7 +66,7 @@ class UioFiles(object): # -------------------------------------------------------------------------- # CLASS FUNCTIONS # -------------------------------------------------------------------------- - def __init__(self, pattern): + def __init__(self, path, pattern): ''' @Description: Assignes a specific pattern for these files. @@ -74,6 +75,9 @@ class UioFiles(object): self: instance of UioFiles Description see class documentation. + path: string + Directory where to list the files. + pattern: string Regular expression pattern. For example: '*.grb' @@ -81,13 +85,16 @@ class UioFiles(object): <nothing> ''' + self.path = path self.pattern = pattern self.files = None + self.__list_files__(self.path) + return #@profiling.timefn - def list_files(self, path, callid=0): + def __list_files__(self, path, callid=0): ''' @Description: Lists all files in the directory with the matching @@ -98,7 +105,7 @@ class UioFiles(object): Description see class documentation. path: string - Directory where to list the files. + Path to the files. callid: integer Id which tells the function if its the first call @@ -128,10 +135,30 @@ class UioFiles(object): # do recursive calls for sub-direcorties if subdirs: for subdir in subdirs: - self.list_files(os.path.join(path, subdir), callid=1) + self.__list_files__(os.path.join(path, subdir), callid=1) return + def __str__(self): + ''' + @Description: + Converts the list of files into a single string. + The entries are sepereated by "," sign. + + @Input: + self: instance of UioFiles + Description see class documentation. + + @Return: + files_string: string + The content of the list as a single string. + ''' + + filenames = [os.path.basename(f) for f in self.files] + files_string = get_list_as_string(filenames, concatenate_sign=', ') + + return files_string + def delete_files(self): ''' @Description: diff --git a/python/_config.py b/python/_config.py new file mode 100644 index 0000000000000000000000000000000000000000..93f29081a11654963f21b5eca89aa71aa036c846 --- /dev/null +++ b/python/_config.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +#******************************************************************************* +# @Author: Anne Philipp (University of Vienna) +# +# @Date: August 2018 +# +# @Change History: +# +# @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. +# +# @Description: +# Contains constant value parameter for flex_extract. +# +#******************************************************************************* + +# ------------------------------------------------------------------------------ +# MODULES +# ------------------------------------------------------------------------------ +import os +import sys +import inspect + + +_VERSION_STR = '7.1' + + +# add path to pythonpath +LOCAL_PYTHON_PATH = os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) +if LOCAL_PYTHON_PATH not in sys.path: + sys.path.append(LOCAL_PYTHON_PATH) diff --git a/python/compilejob.ksh b/python/compilejob.ksh index 3afc4e4e8eb0bb73005f55f7fe720cdabf3fbccc..027c087e944751812bb448e6f6dc99f73ea5640d 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/spatlh00/lh0 +#SBATCH --workdir=/scratch/ms/at/km4a #SBATCH --qos=normal #SBATCH --job-name=flex_ecmwf #SBATCH --output=flex_ecmwf.%j.out @@ -32,11 +32,8 @@ case $HOST in module unload emos module load grib_api/1.14.5 module load emos/437-r64 -export FLEXPART_ROOT_SCRIPTS=$HOME -# export ECMWFDATA=$FLEXPART_ROOT/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python -# export PATH=${PATH}:$ECMWFDATA/python - export MAKEFILE=Makefile.gfortran +export FLEXPART_ROOT_SCRIPTS=${HOME} +export MAKEFILE=Makefile.gfortran ;; *cca*) module switch PrgEnv-cray PrgEnv-intel @@ -48,17 +45,14 @@ 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 ECMWFDATA=$FLEXPART_ROOT/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python -# export PATH=${PATH}:$ECMWFDATA/python - export MAKEFILE=Makefile.CRAY +export FLEXPART_ROOT_SCRIPTS=${HOME} +export MAKEFILE=Makefile.gfortran ;; esac -mkdir -p $FLEXPART_ROOT_SCRIPTS/ECMWFDATA$VERSION -cd $FLEXPART_ROOT_SCRIPTS/ECMWFDATA$VERSION # if FLEXPART_ROOT is not set this means cd to the home directory -tar -xvf $HOME/ECMWFDATA$VERSION.tar +mkdir -p $FLEXPART_ROOT_SCRIPTS/flex_extract_v$VERSION +cd $FLEXPART_ROOT_SCRIPTS/flex_extract_v$VERSION # if FLEXPART_ROOT is not set this means cd to the home directory +tar -xvf $HOME/flex_extract_v$VERSION.tar cd src \rm *.o *.mod CONVERT2 make -f $MAKEFILE >flexcompile 2>flexcompile diff --git a/python/compilejob.temp b/python/compilejob.temp index e699a01daeaa2487c8f3d446652f2d475bd219c5..715308b3d361e2b0f5c088b344ecde539d74243e 100644 --- a/python/compilejob.temp +++ b/python/compilejob.temp @@ -33,9 +33,6 @@ case $HOST in module load grib_api/1.14.5 module load emos/437-r64 export FLEXPART_ROOT_SCRIPTS= -# export ECMWFDATA=$FLEXPART_ROOT/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python -# export PATH=${PATH}:$ECMWFDATA/python export MAKEFILE=Makefile.gfortran ;; *cca*) @@ -49,16 +46,13 @@ case $HOST in export GROUP=`echo $HOME | awk -F / '{print $4}'` export SCRATCH=/scratch/ms/${GROUP}/${USER} export FLEXPART_ROOT_SCRIPTS= -# export ECMWFDATA=$FLEXPART_ROOT/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python -# export PATH=${PATH}:$ECMWFDATA/python export MAKEFILE=Makefile.CRAY ;; esac -mkdir -p $FLEXPART_ROOT_SCRIPTS/ECMWFDATA$VERSION -cd $FLEXPART_ROOT_SCRIPTS/ECMWFDATA$VERSION # if FLEXPART_ROOT is not set this means cd to the home directory -tar -xvf $HOME/ECMWFDATA$VERSION.tar +mkdir -p $FLEXPART_ROOT_SCRIPTS/flex_extract_v$VERSION +cd $FLEXPART_ROOT_SCRIPTS/flex_extract_v$VERSION # if FLEXPART_ROOT is not set this means cd to the home directory +tar -xvf $HOME/flex_extract_v$VERSION.tar cd src \rm *.o *.mod CONVERT2 make -f $MAKEFILE >flexcompile 2>flexcompile diff --git a/python/disaggregation.py b/python/disaggregation.py index 9f285c13a7ab186b96b0625ae73507a9be28b284..aa84eaf0c28110f6772ff3e7a48fd411082d52a4 100644 --- a/python/disaggregation.py +++ b/python/disaggregation.py @@ -1,16 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -#************************************************************************ -# ToDo AP -# - check alist of size 4 ? -# - write a test, IMPORTANT -#************************************************************************ #******************************************************************************* # @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 @@ -28,7 +24,7 @@ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. # # @Module Description: -# disaggregationregation of deaccumulated flux data from an ECMWF model FG field. +# disaggregation of deaccumulated flux data from an ECMWF model FG field. # Initially the flux data to be concerned are: # - large-scale precipitation # - convective precipitation diff --git a/python/get_mars_data.py b/python/get_mars_data.py index 14f6c03f2fbf02b20f52d3d60a20ae04bb1988fb..f059ed4cb6b452b02fc30b7ff14650326a91d552 100755 --- a/python/get_mars_data.py +++ b/python/get_mars_data.py @@ -21,7 +21,7 @@ # (necessary for better documentation with docstrings for later # online documentation) # - use of UIFiles class for file selection and deletion - +# # # @License: # (C) Copyright 2014-2018. @@ -38,6 +38,7 @@ # @Program Content: # - main # - get_mars_data +# - do_retrievement # #******************************************************************************* @@ -55,7 +56,7 @@ except ImportError: ecapi = False # software specific classes and modules from flex_extract -from tools import my_error, normal_exit, interpret_args_and_control +from tools import my_error, normal_exit, get_cmdline_arguments, read_ecenv from EcFlexpart import EcFlexpart from UioFiles import UioFiles @@ -81,9 +82,28 @@ def main(): @Return: <nothing> ''' - args, c = interpret_args_and_control() + + args = get_cmdline_arguments() + + try: + c = ControlFile(args.controlfile) + except IOError: + try: + c = ControlFile(LOCAL_PYTHON_PATH + args.controlfile) + except IOError: + 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' + sys.exit(1) + + env_parameter = read_ecenv(c.ecmwfdatadir + 'python/ECMWF_ENV') + c.assign_args_to_control(args, env_parameter) + c.assign_envs_to_control(env_parameter) + c.check_conditions() + get_mars_data(c) - normal_exit(c) + normal_exit(c.mailfail, 'Done!') return @@ -128,57 +148,60 @@ def get_mars_data(c): c.ecapi = ecapi print 'ecapi: ', c.ecapi + # basetime geht rückwärts + + # if basetime 00 + # dann wird von 12 am vortag bis 00 am start tag geholt + # aber ohne 12 selbst sondern 12 + step + + # if basetime 12 + # dann wird von 00 + step bis 12 am start tag geholt + + # purer forecast wird vorwärts bestimmt. + # purer forecast mode ist dann wenn größer 24 stunden + # wie kann das noch festgestellt werden ???? + # nur FC und steps mehr als 24 ? + # die einzige problematik beim reinen forecast ist die benennung der files! + # also sobald es Tagesüberschneidungen gibt + # allerdings ist das relevant und ersichtlich an den NICHT FLUSS DATEN + + # 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 # 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 + # set time period for one single retrieval datechunk = datetime.timedelta(days=int(c.date_chunk)) + if c.basetime == '00': + start = startm1 + + if c.basetime == '00' or c.basetime == '12': + # endp1 = end + datetime.timedelta(days=1) + endp1 = end + else: + # endp1 = end + datetime.timedelta(days=2) + endp1 = end + datetime.timedelta(days=1) + # -------------- flux data ------------------------------------------------ print 'removing old flux content of ' + c.inputdir - tobecleaned = UioFiles('*_acc_*.' + str(os.getppid()) + '.*.grb') - tobecleaned.list_files(c.inputdir) + tobecleaned = UioFiles(c.inputdir, + '*_acc_*.' + str(os.getppid()) + '.*.grb') tobecleaned.delete_files() - # if forecast for maximum one day (upto 23h) are to be retrieved, + # if forecast for maximum one day (upto 24h) are to be retrieved, # collect accumulation data (flux data) # with additional days in the beginning and at the end # (used for complete disaggregation of original period) - 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") - - print "retrieve " + dates + " in dir " + c.inputdir - - try: - flexpart.retrieve(server, dates, c.inputdir) - except IOError: - my_error(c, 'MARS request failed') - - day += datechunk + if c.maxstep <= 24: + do_retrievement(c, server, startm1, endp1, datechunk, fluxes=True) # if forecast data longer than 24h are to be retrieved, # collect accumulation data (flux data) @@ -186,38 +209,69 @@ def get_mars_data(c): # (disaggregation will be done for the # exact time period with boundary conditions) 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, c.inputdir) - except IOError: - my_error(c, 'MARS request failed') - - day += datechunk + do_retrievement(c, server, start, end, datechunk, fluxes=True) # -------------- non flux data -------------------------------------------- print 'removing old non flux content of ' + c.inputdir - tobecleaned = UioFiles('*__*.' + str(os.getppid()) + '.*.grb') - tobecleaned.list_files(c.inputdir) + tobecleaned = UioFiles(c.inputdir, + '*__*.' + str(os.getppid()) + '.*.grb') tobecleaned.delete_files() + do_retrievement(c, server, start, end, datechunk, fluxes=False) + + return + +def do_retrievement(c, server, start, end, delta_t, fluxes=False): + ''' + @Description: + Divides the complete retrieval period in smaller chunks and + retrieves the data from MARS. + + @Input: + c: instance of 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. + + server: instance of ECMWFService + The server connection to ECMWF + + start: instance of datetime + The start date of the retrieval. + + end: instance of datetime + The end date of the retrieval. + + delta_t: instance of datetime + Delta_t +1 is the maximal time period of a single + retrieval. + + fluxes: boolean, optional + Decides if the flux parameters are to be retrieved or + the rest of the parameter list. + Default value is False. + + @Return: + <nothing> + ''' + + # since actual day also counts as one day, + # we only need to add datechunk - 1 days to retrieval + # for a period + delta_t_m1 = delta_t - datetime.timedelta(days=1) + day = start while day <= end: - # retrieve all non flux MARS data for the whole period - flexpart = EcFlexpart(c, fluxes=False) - tmpday = day + datechunk - datetime.timedelta(days=1) + flexpart = EcFlexpart(c, fluxes) + tmpday = day + delta_t_m1 if tmpday < end: dates = day.strftime("%Y%m%d") + "/to/" + \ tmpday.strftime("%Y%m%d") @@ -230,9 +284,9 @@ def get_mars_data(c): try: flexpart.retrieve(server, dates, c.inputdir) except IOError: - my_error(c, 'MARS request failed') + my_error(c.mailfail, 'MARS request failed') - day += datechunk + day += delta_t return diff --git a/python/install.py b/python/install.py index 835b506faff68461ba5e06f7496b3e9227b24074..31074b3305e4d90e69db5ebea450f9b4e009b9e8 100755 --- a/python/install.py +++ b/python/install.py @@ -1,11 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -#************************************************************************ -# ToDo AP -# - 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: Leopold Haimberger (University of Vienna) # @@ -36,8 +30,14 @@ # # @Program Content: # - main -# - install_args_and_control +# - get_install_cmdline_arguments # - install_via_gateway +# - mk_tarball +# - mk_env_vars +# - mk_compilejob +# - mk_job_template +# - delete_convert_build +# - make_convert_build # #******************************************************************************* @@ -46,13 +46,14 @@ # ------------------------------------------------------------------------------ import os import sys -import glob import subprocess import inspect from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter # software specific classes and modules from flex_extract from ControlFile import ControlFile +from UioFiles import UioFiles +from tools import make_dir, put_file_to_ecserver, submit_job_to_ecserver # add path to pythonpath so that python finds its buddies LOCAL_PYTHON_PATH = os.path.dirname(os.path.abspath( @@ -60,6 +61,8 @@ LOCAL_PYTHON_PATH = os.path.dirname(os.path.abspath( if LOCAL_PYTHON_PATH not in sys.path: sys.path.append(LOCAL_PYTHON_PATH) +_VERSION_STR = '7.1' + # ------------------------------------------------------------------------------ # FUNCTIONS # ------------------------------------------------------------------------------ @@ -77,24 +80,29 @@ def main(): ''' os.chdir(LOCAL_PYTHON_PATH) - args, c = install_args_and_control() + args = get_install_cmdline_arguments() - 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' + try: + c = ControlFile(args.controlfile) + except IOError: + 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' + exit(1) - sys.exit() + c.assign_args_to_control(args) + c.check_install_conditions() - return + install_via_gateway(c) + return -def install_args_and_control(): +def get_install_cmdline_arguments(): ''' @Description: - Assigns the command line arguments for installation and reads - CONTROL file content. Apply default values for non mentioned arguments. + Decomposes the command line arguments and assigns them to variables. + Apply default values for non mentioned arguments. @Input: <nothing> @@ -102,42 +110,32 @@ def install_args_and_control(): @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, FLEXPARTDIR - For more information about format and content of the parameter see - documentation. ''' - parser = ArgumentParser(description='Install ECMWFDATA software locally or \ + parser = ArgumentParser(description='Install flex_extract software locally or \ on ECMWF machines', formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument('--target', dest='install_target', + parser.add_argument('--target', dest='install_target', default=None, help="Valid targets: local | ecgate | cca , \ the latter two are at ECMWF") - parser.add_argument("--makefile", dest="makefile", + parser.add_argument("--makefile", dest="makefile", default=None, help='Name of Makefile to use for compiling CONVERT2') - parser.add_argument("--ecuid", dest="ecuid", + parser.add_argument("--ecuid", dest="ecuid", default=None, help='user id at ECMWF') - parser.add_argument("--ecgid", dest="ecgid", + parser.add_argument("--ecgid", dest="ecgid", default=None, help='group id at ECMWF') - parser.add_argument("--gateway", dest="gateway", + parser.add_argument("--gateway", dest="gateway", default=None, help='name of local gateway server') - parser.add_argument("--destination", dest="destination", + parser.add_argument("--destination", dest="destination", default=None, help='ecaccess destination, e.g. leo@genericSftp') parser.add_argument("--flexpart_root_scripts", dest="flexpart_root_scripts", - help="FLEXPART root directory on ECMWF servers \ - (to find grib2flexpart and COMMAND file)\n\ - Normally ECMWFDATA resides in the scripts directory \ + default=None, help="FLEXPART root directory on ECMWF \ + servers (to find grib2flexpart and COMMAND file)\n\ + Normally flex_extract resides in the scripts directory \ of the FLEXPART distribution, thus the:") -# arguments for job submission to ECMWF, only needed by submit.py + # arguments for job submission to ECMWF, only needed by submit.py parser.add_argument("--job_template", dest='job_template', default="job.temp.o", help="job template file for submission to ECMWF") @@ -148,53 +146,10 @@ def install_args_and_control(): args = parser.parse_args() - try: - c = ControlFile(args.controlfile) - except IOError: - 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' - exit(1) + return args + - if args.install_target != 'local': - if args.ecgid is None or args.ecuid is None or args.gateway is None \ - or args.destination is None: - print 'Please enter your ECMWF user id and group id as well as \ - the \nname of the local gateway and the ectrans \ - destination ' - print 'with command line options --ecuid --ecgid \ - --gateway --destination' - print 'Try "' + sys.argv[0].split('/')[-1] + \ - ' -h" to print usage information' - print 'Please consult ecaccess documentation or ECMWF user support \ - for further details' - sys.exit(1) - else: - c.ecuid = args.ecuid - c.ecgid = args.ecgid - c.gateway = args.gateway - c.destination = args.destination - - if args.makefile: - c.makefile = args.makefile - - if args.install_target == 'local': - if args.flexpart_root_scripts is None: - c.flexpart_root_scripts = '../' - else: - c.flexpart_root_scripts = args.flexpart_root_scripts - - if args.install_target != 'local': - if args.flexpart_root_scripts is None: - c.ec_flexpart_root_scripts = '${HOME}' - else: - c.ec_flexpart_root_scripts = args.flexpart_root_scripts - - return args, c - - -def install_via_gateway(c, target): +def install_via_gateway(c): ''' @Description: Perform the actual installation on local machine or prepare data @@ -212,193 +167,326 @@ def install_via_gateway(c, target): For more information about format and content of the parameter see documentation. + @Return: + <nothing> + ''' + + ecd = c.ecmwfdatadir + tarball_name = 'flex_extract_v' + _VERSION_STR + '.tar' + target_dir = 'flex_extract_v' + _VERSION_STR + fortran_executable = 'CONVERT2' + + if c.install_target.lower() != 'local': + + mk_compilejob(ecd + 'python/compilejob.temp', c.makefile, + c.install_target, c.ecuid, c.ecgid, + c.flexpart_root_scripts) + + mk_job_template(ecd + 'python/job.temp.o', c.ecuid, c.ecgid, c.gateway, + c.destination, c.flexpart_root_scripts) + + mk_env_vars(ecd, c.ecuid, c.ecgid, c.gateway, c.destination) + + #os.chdir('/') + + mk_tarball(ecd, tarball_name) + + put_file_to_ecserver(ecd, tarball_name, c.install_target, + c.ecuid, c.ecgid) + + submit_job_to_ecserver(ecd + '/python/', c.install_target, + 'compilejob.ksh') + + print 'job compilation script has been submitted to ecgate for ' + \ + 'installation in ' + c.flexpart_root_scripts + \ + '/' + target_dir + print 'You should get an email with subject flexcompile within ' + \ + 'the next few minutes!' + + else: #local + if not c.flexpart_root_scripts or c.flexpart_root_scripts == '../': + print 'WARNING: FLEXPART_ROOT_SCRIPTS has not been specified' + print 'There will be only the compilation of ' + \ + ' in ' + ecd + '/src' + os.chdir(ecd + '/src') + else: # creates the target working directory for flex_extract + c.flexpart_root_scripts = os.path.expandvars(os.path.expanduser( + c.flexpart_root_scripts)) + if os.path.abspath(ecd) != os.path.abspath(c.flexpart_root_scripts): + os.chdir('/') + mk_tarball(ecd, tarball_name) + make_dir(c.flexpart_root_scripts + '/' + target_dir) + os.chdir(c.flexpart_root_scripts + '/' + target_dir) + print 'Untar ...' + subprocess.check_call(['tar', '-xvf', + ecd + '../' + tarball_name]) + os.chdir(c.flexpart_root_scripts + '/' + target_dir + '/src') + + # Create Fortran executable - CONVERT2 + print 'Install ' + target_dir + ' software on ' + \ + c.install_target + ' in directory ' + \ + os.path.abspath(os.getcwd() + '/../') + '\n' + + delete_convert_build('') + make_convert_build('', c.makefile, fortran_executable) + + return + +def mk_tarball(ecd, tarname): + ''' + @Description: + Creates a tarball from all files which need to be sent to the + installation directory. + It does not matter if this is local or remote. + Collects all python files, the Fortran source and makefiles, + the ECMWF_ENV file, the CONTROL files as well as + the korn shell and template files. + + @Input: + ecd: string + The path were the file is to be stored. + + tarname: string + The name of the file to send to the ECMWF server. + + @Return: + <nothing> + ''' + + print 'Create tarball ...' + try: + subprocess.check_call(['tar -cvf '+ + ecd + '../' + tarname + ' ' + + ecd + 'python/*py ' + + ecd + 'python/CONTROL* ' + + ecd + 'python/*ksh ' + + ecd + 'python/*temp* ' + + ecd + 'python/ECMWF_ENV ' + + ecd + 'grib_templates ' + + ecd + 'src/*.f ' + + ecd + 'src/*.f90 ' + + ecd + 'src/*.h ' + + ecd + 'src/Makefile*'], shell=True) + except subprocess.CalledProcessError as e: + print 'ERROR:' + print e.output + sys.exit('could not make installation tar ball!') + + return + +def mk_env_vars(ecd, ecuid, ecgid, gateway, destination): + ''' + @Description: + Creates a file named ECMWF_ENV which contains the + necessary environmental variables at ECMWF servers. + + @Input: + ecd: string + The path were the file is to be stored. + + ecuid: string + The user id on ECMWF server. + + ecgid: string + The group id on ECMWF server. + + gateway: string + The gateway server the user is using. + + destination: string + The remote destination which is used to transfer files + from ECMWF server to local gateway server. + + @Return: + <nothing> + ''' + + with open(ecd + 'python/ECMWF_ENV', 'w') as fo: + fo.write('ECUID ' + ecuid + '\n') + fo.write('ECGID ' + ecgid + '\n') + fo.write('GATEWAY ' + gateway + '\n') + fo.write('DESTINATION ' + destination + '\n') + + return + +def mk_compilejob(template, makefile, target, ecuid, ecgid, fp_root): + ''' + @Description: + Modifies the original job template file so that it is specified + for the user and the environment were it will be applied. Result + is stored in a new file "job.temp" in the python directory. + + @Input: + template: string + File which contains the original text for the job template. + It must contain the complete path to the file. + + makefile: string + Name of the makefile which should be used to compile FORTRAN + CONVERT2 program. + target: string - The target where the installation should be processed. - E.g. "local", "ecgate" or "cca" + The target where the installation should be done, e.g. the queue. + + ecuid: string + The user id on ECMWF server. + + ecgid: string + The group id on ECMWF server. + + fp_root: string + Path to the root directory of FLEXPART environment or flex_extract + environment. @Return: <nothing> ''' - ecd = c.ecmwfdatadir - template = ecd + 'python/compilejob.temp' - job = ecd + 'python/compilejob.ksh' - fo = open(job, 'w') -#AP could do with open(template) as f, open(job, 'w') as fo: -#AP or nested with statements + with open(template) as f: fdata = f.read().split('\n') + + with open(template[:-4] + 'ksh', 'w') as fo: for data in fdata: if 'MAKEFILE=' in data: - if c.makefile is not None: - data = 'export MAKEFILE=' + c.makefile - if 'FLEXPART_ROOT_SCRIPTS=' in data: - if c.flexpart_root_scripts != '../': - data = 'export FLEXPART_ROOT_SCRIPTS=' + \ - c.flexpart_root_scripts + data = 'export MAKEFILE=' + makefile + elif 'FLEXPART_ROOT_SCRIPTS=' in data: + if fp_root != '../': + data = 'export FLEXPART_ROOT_SCRIPTS=' + fp_root else: data = 'export FLEXPART_ROOT_SCRIPTS=$HOME' - if target.lower() != 'local': + elif target.lower() != 'local': if '--workdir' in data: - data = '#SBATCH --workdir=/scratch/ms/' + c.ecgid + \ - '/' + c.ecuid - if '##PBS -o' in data: - data = '##PBS -o /scratch/ms/' + c.ecgid + '/' + c.ecuid + \ - 'flex_ecmwf.$Jobname.$Job_ID.out' - if 'FLEXPART_ROOT_SCRIPTS=' in data: - if c.ec_flexpart_root_scripts != '../': - data = 'export FLEXPART_ROOT_SCRIPTS=' + \ - c.ec_flexpart_root_scripts + data = '#SBATCH --workdir=/scratch/ms/' + \ + ecgid + '/' + ecuid + elif '##PBS -o' in data: + data = '##PBS -o /scratch/ms/' + ecgid + '/' + ecuid + \ + 'flex_ecmwf.$Jobname.$Job_ID.out' + elif 'FLEXPART_ROOT_SCRIPTS=' in data: + if fp_root != '../': + data = 'export FLEXPART_ROOT_SCRIPTS=' + fp_root else: data = 'export FLEXPART_ROOT_SCRIPTS=$HOME' fo.write(data + '\n') - f.close() - fo.close() - - if target.lower() != 'local': - template = ecd + 'python/job.temp.o' -#AP hier eventuell Zeile für Zeile lesen und dann if Entscheidung - with open(template) as f: - fdata = f.read().split('\n') - f.close() - fo = open(template[:-2], 'w') + + return + +def mk_job_template(template, ecuid, ecgid, gateway, destination, fp_root): + ''' + @Description: + Modifies the original job template file so that it is specified + for the user and the environment were it will be applied. Result + is stored in a new file "job.temp" in the python directory. + + @Input: + template: string + File which contains the original text for the job template. + It must contain the complete path to the file. + + ecuid: string + The user id on ECMWF server. + + ecgid: string + The group id on ECMWF server. + + gateway: string + The gateway server the user is using. + + destination: string + The remote destination which is used to transfer files + from ECMWF server to local gateway server. + + fp_root: string + Path to the root directory of FLEXPART environment or flex_extract + environment. + + @Return: + <nothing> + ''' + + with open(template) as f: + fdata = f.read().split('\n') + + with open(template[:-2], 'w') as fo: for data in fdata: if '--workdir' in data: - data = '#SBATCH --workdir=/scratch/ms/' + c.ecgid + \ - '/' + c.ecuid - if '##PBS -o' in data: - data = '##PBS -o /scratch/ms/' + c.ecgid + '/' + \ - c.ecuid + 'flex_ecmwf.$Jobname.$Job_ID.out' - if 'export PATH=${PATH}:' in data: - data += c.ec_flexpart_root_scripts + '/ECMWFDATA7.1/python' - if 'cat>>' in data or 'cat >>' in data: - i = data.index('>') - fo.write(data[:i] + data[i+1:] + '\n') - fo.write('GATEWAY ' + c.gateway + '\n') - fo.write('DESTINATION ' + c.destination + '\n') - fo.write('EOF\n') + data = '#SBATCH --workdir=/scratch/ms/' + ecgid + \ + '/' + ecuid + elif '##PBS -o' in data: + data = '##PBS -o /scratch/ms/' + ecgid + '/' + \ + ecuid + 'flex_ecmwf.$Jobname.$Job_ID.out' + elif 'export PATH=${PATH}:' in data: + data += fp_root + '/flex_extract_v7.1/python' fo.write(data + '\n') - fo.close() - - job = ecd + 'python/ECMWF_ENV' - with open(job, 'w') as fo: - fo.write('ECUID ' + c.ecuid + '\n') - fo.write('ECGID ' + c.ecgid + '\n') - fo.write('GATEWAY ' + c.gateway + '\n') - fo.write('DESTINATION ' + c.destination + '\n') - fo.close() - - if target.lower() == 'local': - # compile CONVERT2 - if c.flexpart_root_scripts is None or c.flexpart_root_scripts == '../': - print 'Warning: FLEXPART_ROOT_SCRIPTS has not been specified' - print 'Only CONVERT2 will be compiled in ' + ecd + '/../src' - else: - c.flexpart_root_scripts = os.path.expandvars(os.path.expanduser( - c.flexpart_root_scripts)) - if os.path.abspath(ecd) != os.path.abspath(c.flexpart_root_scripts): - os.chdir('/') - p = subprocess.check_call(['tar', '-cvf', - ecd + '../ECMWFDATA7.1.tar', - ecd + 'python', - ecd + 'grib_templates', - ecd + 'src']) - try: - os.makedirs(c.flexpart_root_scripts + '/ECMWFDATA7.1') - finally: - pass - os.chdir(c.flexpart_root_scripts + '/ECMWFDATA7.1') - p = subprocess.check_call(['tar', '-xvf', - ecd + '../ECMWFDATA7.1.tar']) - os.chdir(c.flexpart_root_scripts + '/ECMWFDATA7.1/src') - - os.chdir('../src') - print(('install ECMWFDATA7.1 software on ' + target + ' in directory ' - + os.getcwd())) - if c.makefile is None: - makefile = 'Makefile.local.ifort' - else: - makefile = c.makefile - flist = glob.glob('*.mod') + glob.glob('*.o') - if flist: - p = subprocess.check_call(['rm'] + flist) - try: - print 'Using makefile: ' + makefile - p = subprocess.check_call(['make', '-f', makefile]) - p = subprocess.check_call(['ls', '-l', 'CONVERT2']) - except subprocess.CalledProcessError as e: - print 'compile failed with the following error:' - print e.output - print 'please edit ' + makefile + \ + return + +def delete_convert_build(ecd): + ''' + @Description: + Clean up the Fortran source directory and remove all + build files (e.g. *.o, *.mod and CONVERT2) + + @Input: + ecd: string + The path to the Fortran program. + + @Return: + <nothing> + ''' + + modfiles = UioFiles(ecd, '*.mod') + objfiles = UioFiles(ecd, '*.o') + exefile = UioFiles(ecd, 'CONVERT2') + + modfiles.delete_files() + objfiles.delete_files() + exefile.delete_files() + + return + +def make_convert_build(ecd, makefile, f_executable): + ''' + @Description: + Compiles the Fortran code and generates the executable. + + @Input: + ecd: string + The path were the file is to be stored. + + makefile: string + The name of the makefile which should be used. + + f_executable: string + The name of the executable the Fortran program generates after + compilation. + + @Return: + <nothing> + ''' + + try: + print 'Using makefile: ' + makefile + p = subprocess.Popen(['make', '-f', ecd + makefile], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=1) + pout, perr = p.communicate() + print pout + if p.returncode != 0: + print perr + print 'Please edit ' + makefile + \ ' or try another Makefile in the src directory.' - print 'most likely GRIB_API_INCLUDE_DIR, GRIB_API_LIB \ - and EMOSLIB must be adapted.' + print 'Most likely GRIB_API_INCLUDE_DIR, GRIB_API_LIB ' \ + 'and EMOSLIB must be adapted.' print 'Available Makefiles:' - print glob.glob('Makefile*') - elif target.lower() == 'ecgate': - os.chdir('/') - p = subprocess.check_call(['tar', '-cvf', - ecd + '../ECMWFDATA7.1.tar', - ecd + 'python', - ecd + 'grib_templates', - ecd + 'src']) - try: - p = subprocess.check_call(['ecaccess-file-put', - ecd + '../ECMWFDATA7.1.tar', - 'ecgate:/home/ms/' + c.ecgid + '/' + - c.ecuid + '/ECMWFDATA7.1.tar']) - except subprocess.CalledProcessError as e: - print 'ecaccess-file-put failed! \ - Probably the eccert key has expired.' - exit(1) - - try: - p = subprocess.check_call(['ecaccess-job-submit', - '-queueName', - target, - ecd + 'python/compilejob.ksh']) - print 'compilejob.ksh has been submitted to ecgate for \ - installation in ' + c.ec_flexpart_root_scripts + \ - '/ECMWFDATA7.1' - print 'You should get an email with subject flexcompile within \ - the next few minutes' - except subprocess.CalledProcessError as e: - print 'ecaccess-job-submit failed!' - exit(1) - - elif target.lower() == 'cca': - os.chdir('/') - p = subprocess.check_call(['tar', '-cvf', - ecd + '../ECMWFDATA7.1.tar', - ecd + 'python', - ecd + 'grib_templates', - ecd + 'src']) - try: - p = subprocess.check_call(['ecaccess-file-put', - ecd + '../ECMWFDATA7.1.tar', - 'cca:/home/ms/' + c.ecgid + '/' + - c.ecuid + '/ECMWFDATA7.1.tar']) - except subprocess.CalledProcessError as e: - print 'ecaccess-file-put failed! \ - Probably the eccert key has expired.' - exit(1) - - try: - p = subprocess.check_call(['ecaccess-job-submit', - '-queueName', - target, - ecd + 'python/compilejob.ksh']) - print 'compilejob.ksh has been submitted to cca for installation in ' +\ - c.ec_flexpart_root_scripts + '/ECMWFDATA7.1' - print 'You should get an email with subject flexcompile \ - within the next few minutes' - except subprocess.CalledProcessError as e: - print 'ecaccess-job-submit failed!' - exit(1) - + print UioFiles('.', 'Makefile*') + sys.exit('Compilation failed!') + except ValueError as e: + print 'ERROR: Makefile call failed:' + print e else: - print 'ERROR: unknown installation target ', target - print 'Valid targets: ecgate, cca, local' + subprocess.check_call(['ls', '-l', ecd + f_executable]) return diff --git a/python/job.ksh b/python/job.ksh index 243d74656f3b5a76e7f3adbb5e8884c6f539297a..8cbc1d82d16626d48e938bd80ce8690705a43828 100644 --- a/python/job.ksh +++ b/python/job.ksh @@ -32,9 +32,7 @@ case $HOST in module unload emos module load grib_api/1.14.5 module load emos/437-r64 -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python - export PATH=${PATH}:${HOME}/ECMWFDATA7.1/python + export PATH=${PATH}:${HOME}/flex_extract_v7.1/python ;; *cca*) module switch PrgEnv-cray PrgEnv-intel @@ -42,17 +40,8 @@ case $HOST in module load emos module load python export SCRATCH=$TMPDIR -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python - export PATH=${PATH}:${HOME}/ECMWFDATA7.1/python + export PATH=${PATH}:${HOME}/flex_extract_v7.1/python ;; -# *) -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PATH=/opt/anaconda/bin:$ECMWFDATA/python:${PATH} -# export PYTHONPATH=/opt/anaconda/lib/python2.7/site-packages/grib_api:$ECMWFDATA/python -# export SCRATCH=$ECMWFDATA/python -# which python -# ;; esac cd $SCRATCH @@ -62,34 +51,41 @@ cd python$$ export CONTROL=CONTROL cat >$CONTROL<<EOF -GATEWAY srvx8.img.univie.ac.at -DESTINATION annep@genericSftp -accuracy 16 +accuracy 24 addpar 186 187 188 235 139 39 +area basetime None +controlfile CONTROL.test cwc 0 date_chunk 3 -debug 1 +debug 0 +destination annep@genericSftp dpdeta 1 dtime 3 ecfsdir ectmp:/${USER}/econdemand/ +ecgid at ecstorage 0 ectrans 1 -end_date 20160809 +ecuid km4a +end_date 20000101 eta 0 etadiff 0 +etapar 77 expver 1 format GRIB1 +gateway srvx8.img.univie.ac.at gauss 1 grib2flexpart 0 grid 5000 inputdir ../work +install_target None +job_template job.temp left -15000 level 60 levelist 55/to/60 lower 30000 -mailfail ${USER} -mailops ${USER} +mailfail ${USER} +mailops ${USER} makefile None marsclass EI maxstep 11 @@ -97,16 +93,18 @@ number OFF omega 0 omegadiff 0 outputdir ../work -prefix EI +prefix EItest_ +queue ecgate resol 63 right 45000 smooth 0 -start_date 20160809 +start_date 20000101 step 00 01 02 03 04 05 00 07 08 09 10 11 00 01 02 03 04 05 00 07 08 09 10 11 stream OPER time 00 00 00 00 00 00 06 00 00 00 00 00 12 12 12 12 12 12 18 12 12 12 12 12 type AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC upper 75000 +wrf 0 EOF diff --git a/python/job.temp b/python/job.temp index 4a20430215cdcb51cca4cdf6604f719353d35676..e8ce37658f0f12745bee16445498904c464fe929 100644 --- a/python/job.temp +++ b/python/job.temp @@ -32,9 +32,7 @@ case $HOST in module unload emos module load grib_api/1.14.5 module load emos/437-r64 -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python - export PATH=${PATH}:${HOME}/ECMWFDATA7.1/python + export PATH=${PATH}:${HOME}/flex_extract_v7.1/python ;; *cca*) module switch PrgEnv-cray PrgEnv-intel @@ -42,17 +40,8 @@ case $HOST in module load emos module load python export SCRATCH=$TMPDIR -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python - export PATH=${PATH}:${HOME}/ECMWFDATA7.1/python + export PATH=${PATH}:${HOME}/flex_extract_v7.1/python ;; -# *) -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PATH=/opt/anaconda/bin:$ECMWFDATA/python:${PATH} -# export PYTHONPATH=/opt/anaconda/lib/python2.7/site-packages/grib_api:$ECMWFDATA/python -# export SCRATCH=$ECMWFDATA/python -# which python -# ;; esac cd $SCRATCH @@ -62,10 +51,6 @@ cd python$$ export CONTROL=CONTROL cat >$CONTROL<<EOF -GATEWAY srvx8.img.univie.ac.at -DESTINATION annep@genericSftp -EOF -cat >>$CONTROL<<EOF EOF diff --git a/python/job.temp.o b/python/job.temp.o index b3ff36c1202bc7c22f29837bb8a072a1f40844b2..fbdbd81aa1ea7100a84ac0858ed29804526e98a5 100644 --- a/python/job.temp.o +++ b/python/job.temp.o @@ -32,8 +32,6 @@ case $HOST in module unload emos module load grib_api/1.14.5 module load emos/437-r64 -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python export PATH=${PATH}: ;; *cca*) @@ -42,17 +40,8 @@ case $HOST in module load emos module load python export SCRATCH=$TMPDIR -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python export PATH=${PATH}: ;; -# *) -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PATH=/opt/anaconda/bin:$ECMWFDATA/python:${PATH} -# export PYTHONPATH=/opt/anaconda/lib/python2.7/site-packages/grib_api:$ECMWFDATA/python -# export SCRATCH=$ECMWFDATA/python -# which python -# ;; esac cd $SCRATCH @@ -61,7 +50,7 @@ cd python$$ export CONTROL=CONTROL -cat >>$CONTROL<<EOF +cat >$CONTROL<<EOF EOF diff --git a/python/joboper.ksh b/python/joboper.ksh index 8bb5f874dc3f2100d6009dc106af849e8ac3a46e..7eb5a8093c50aa26a1ba1f58eeba96f6cd669bbc 100644 --- a/python/joboper.ksh +++ b/python/joboper.ksh @@ -32,9 +32,7 @@ case $HOST in module unload emos module load grib_api/1.14.5 module load emos/437-r64 -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python - export PATH=${PATH}:${HOME}/ECMWFDATA7.1/python + export PATH=${PATH}:${HOME}/flex_extract_v7.1/python ;; *cca*) module switch PrgEnv-cray PrgEnv-intel @@ -42,17 +40,8 @@ case $HOST in module load emos module load python export SCRATCH=$TMPDIR -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PYTHONPATH=$ECMWFDATA/python - export PATH=${PATH}:${HOME}/ECMWFDATA7.1/python + export PATH=${PATH}:${HOME}/flex_extract_v7.1/python ;; -# *) -# export ECMWFDATA=$HOME/ECMWFDATA$VERSION -# export PATH=/opt/anaconda/bin:$ECMWFDATA/python:${PATH} -# export PYTHONPATH=/opt/anaconda/lib/python2.7/site-packages/grib_api:$ECMWFDATA/python -# export SCRATCH=$ECMWFDATA/python -# which python -# ;; esac cd $SCRATCH @@ -66,30 +55,40 @@ GATEWAY srvx8.img.univie.ac.at DESTINATION annep@genericSftp accuracy 16 addpar 186 187 188 235 139 39 +area +base_time ${MSJ_BASETIME} basetime None +controlfile CONTROL.temp cwc 0 date_chunk 3 debug 1 +destination None dpdeta 1 dtime 3 ecfsdir ectmp:/${USER}/econdemand/ +ecgid None ecstorage 0 ectrans 1 -start_date ${MSJ_YEAR}${MSJ_MONTH}${MSJ_DAY} +ecuid None +end_date ${MSJ_YEAR}${MSJ_MONTH}${MSJ_DAY} eta 0 etadiff 0 +etapar 77 expver 1 format GRIB1 +gateway None gauss 1 grib2flexpart 0 grid 5000 inputdir ../work +install_target None +job_template job.temp left -15000 level 60 levelist 55/to/60 lower 30000 -mailfail ${USER} -mailops ${USER} +mailfail ${USER} +mailops ${USER} makefile None marsclass EI maxstep 11 @@ -98,6 +97,7 @@ omega 0 omegadiff 0 outputdir ../work prefix EI +queue ecgate resol 63 right 45000 smooth 0 @@ -107,6 +107,7 @@ stream OPER time 00 00 00 00 00 00 06 00 00 00 00 00 12 12 12 12 12 12 18 12 12 12 12 12 type AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC AN FC FC FC FC FC upper 75000 +wrf 0 EOF diff --git a/python/mk_install_tar.sh b/python/mk_install_tar.sh new file mode 100755 index 0000000000000000000000000000000000000000..f13f82ecc1ae6b5ff36b67d3c415418c175dbb01 --- /dev/null +++ b/python/mk_install_tar.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# +# @Author: Anne Philipp +# +# @Date: June, 7 2018 +# +# @Description: Makes a tarball for installation +# + +tarname='flex_extract_v7.1.tar.gz' +path='/nas/tmc/Anne/Interpolation/flexextract/flexextract/python/../' + +tar -cvf $tarname ${path}python/*py ${path}python/CONTROL* ${path}python/*ksh ${path}python/*temp ${path}python/ECMWF_ENV ${path}python/*json ${path}grib_templates ${path}src/*.f ${path}src/*.f90 ${path}src/*.h ${path}src/Makefile* + + diff --git a/python/plot_retrieved.py b/python/plot_retrieved.py index d924c357e5a21569af4bf88dcdf857de20301c53..45e7bb2e7783cfe0644b1fe2eb4c296d1bf75fe2 100755 --- a/python/plot_retrieved.py +++ b/python/plot_retrieved.py @@ -1,11 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -#************************************************************************ -# ToDo AP -# - documentation der Funktionen -# - docu der progam functionality -# - apply pep8 -#************************************************************************ #******************************************************************************* # @Author: Leopold Haimberger (University of Vienna) # @@ -33,6 +27,7 @@ # @Program Content: # - main # - get_basics +# - get_files_per_date # - plot_retrieved # - plot_timeseries # - plot_map @@ -218,8 +213,7 @@ def plot_retrieved(c): c.levels = np.asarray(c.levels, dtype='int') c.area = np.asarray(c.area) - files = UioFiles(c.prefix+'*') - files.list_files(c.inputdir) + files = UioFiles(c.inputdir, c.prefix+'*') ifiles = get_files_per_date(files.files, datelist) ifiles.sort() @@ -621,7 +615,7 @@ def get_plot_args(): 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 \ + Normally flex_extract resides in the scripts directory \ of the FLEXPART distribution") parser.add_argument("--controlfile", dest="controlfile", diff --git a/python/prepare_flexpart.py b/python/prepare_flexpart.py index 2e5a160a52252fa2a45809fc506ccf7940543a2c..088c2a0fd49e2cc9452d622640b8b0c137b47f07 100755 --- a/python/prepare_flexpart.py +++ b/python/prepare_flexpart.py @@ -1,10 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -#************************************************************************ -# ToDo AP -# - wieso start=startm1 wenn basetime = 0 ? wenn die fluxes nicht mehr -# relevant sind? verstehe ich nicht -#************************************************************************ #******************************************************************************* # @Author: Anne Fouilloux (University of Oslo) # @@ -61,10 +56,11 @@ import os import inspect import sys import socket +import _config # software specific classes and modules from flex_extract from UioFiles import UioFiles -from tools import interpret_args_and_control, clean_up +from tools import clean_up, get_cmdline_arguments, read_ecenv from EcFlexpart import EcFlexpart ecapi = 'ecmwf' not in socket.gethostname() @@ -80,6 +76,7 @@ LOCAL_PYTHON_PATH = os.path.dirname(os.path.abspath( if LOCAL_PYTHON_PATH not in sys.path: sys.path.append(LOCAL_PYTHON_PATH) + # ------------------------------------------------------------------------------ # FUNCTION # ------------------------------------------------------------------------------ @@ -96,12 +93,30 @@ def main(): @Return: <nothing> ''' - args, c = interpret_args_and_control() - prepare_flexpart(args, c) + + args = get_cmdline_arguments() + + try: + c = ControlFile(args.controlfile) + except IOError: + try: + c = ControlFile(LOCAL_PYTHON_PATH + args.controlfile) + except IOError: + 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' + sys.exit(1) + + env_parameter = read_ecenv(c.ecmwfdatadir + 'python/ECMWF_ENV') + c.assign_args_to_control(args, env_parameter) + c.assign_envs_to_control(env_parameter) + c.check_conditions() + prepare_flexpart(args.ppid, c) return -def prepare_flexpart(args, c): +def prepare_flexpart(ppid, c): ''' @Description: Lists all grib files retrieved from MARS with get_mars_data and @@ -111,8 +126,9 @@ def prepare_flexpart(args, c): a file with a specific FLEXPART relevant naming convention. @Input: - args: instance of ArgumentParser - Contains the commandline arguments from script/program call. + ppid: int + Contains the ppid number of the current ECMWF job. If it is called + from this script, it is "None". c: instance of class ControlFile Contains all the parameters of CONTROL file, which are e.g.: @@ -131,10 +147,10 @@ def prepare_flexpart(args, c): <nothing> ''' - if not args.ppid: + if not ppid: c.ppid = str(os.getppid()) else: - c.ppid = args.ppid + c.ppid = ppid c.ecapi = ecapi @@ -147,38 +163,32 @@ def prepare_flexpart(args, c): month=int(c.end_date[4:6]), day=int(c.end_date[6:])) - # to deaccumulate the fluxes correctly - # one day ahead of the start date and - # one day after the end date is needed - startm1 = start - datetime.timedelta(days=1) -# endp1 = end + datetime.timedelta(days=1) + # assign starting date minus 1 day + # since for basetime 00 we need the 12 hours upfront + # (the day before from 12 UTC to current day 00 UTC) + if c.basetime == '00': + start = start - datetime.timedelta(days=1) - # get all files with flux data to be deaccumulated - inputfiles = UioFiles('*OG_acc_SL*.' + c.ppid + '.*') - inputfiles.list_files(c.inputdir) + print 'Prepare ' + start.strftime("%Y%m%d") + \ + "/to/" + end.strftime("%Y%m%d") # create output dir if necessary if not os.path.exists(c.outputdir): os.makedirs(c.outputdir) + # get all files with flux data to be deaccumulated + inputfiles = UioFiles(c.inputdir, '*OG_acc_SL*.' + c.ppid + '.*') + # deaccumulate the flux data flexpart = EcFlexpart(c, fluxes=True) flexpart.write_namelist(c, 'fort.4') flexpart.deacc_fluxes(inputfiles, c) - print 'Prepare ' + start.strftime("%Y%m%d") + \ - "/to/" + end.strftime("%Y%m%d") - # get a list of all files from the root inputdir - inputfiles = UioFiles('????__??.*' + c.ppid + '.*') - inputfiles.list_files(c.inputdir) + inputfiles = UioFiles(c.inputdir, '????__??.*' + c.ppid + '.*') - # produce FLEXPART-ready GRIB files and - # process GRIB files - + # produce FLEXPART-ready GRIB files and process them - # copy/transfer/interpolate them or make them GRIB2 - if c.basetime == '00': - start = startm1 - flexpart = EcFlexpart(c, fluxes=False) flexpart.create(inputfiles, c) flexpart.process_output(c) @@ -186,7 +196,7 @@ def prepare_flexpart(args, c): # check if in debugging mode, then store all files # otherwise delete temporary files if int(c.debug) != 0: - print 'Temporary files left intact' + print '\nTemporary files left intact' else: clean_up(c) diff --git a/python/profiling.py b/python/profiling.py index 526c17fc5d4e0e2de413530ea7189542521f1435..4511af2aca3a41265a9dd035b11430e84626ac62 100644 --- a/python/profiling.py +++ b/python/profiling.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- #************************************************************************ # ToDo AP -# - check of license of book content +# - check license of book content #************************************************************************ #******************************************************************************* # diff --git a/python/set_CTBTO_env.csh b/python/set_CTBTO_env.csh deleted file mode 100644 index f504d866db9643c13bcee4563b938520a50fd306..0000000000000000000000000000000000000000 --- a/python/set_CTBTO_env.csh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/csh - -# script to prepare CTBTO environment for -# ECMWFDATA7.0 -# -# Leo Haimberger 1.3.2016 - -setenv PATH /dvl/atm/klinkl/software/local/bin:$PATH -setenv PYTHONPATH /dvl/atm/klinkl/software/local/lib/python2.7/site-packages/grib_api -setenv LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:/dvl/atm/klinkl/software/local/lib/ -setenv GRIB_API_INCLUDE_DIR /dvl/atm/klinkl/software/local/include/ -setenv GRIB_API_LIB '-L/dvl/atm/klinkl/software/local/lib -Bstatic -lgrib_api_f77 -lgrib_api_f90 -lgrib_api -lemosR64 -Bdynamic -lm -ljasper' diff --git a/python/set_CTBTO_env.sh b/python/set_CTBTO_env.sh deleted file mode 100644 index 078cee7cbf3124a94cfb3a9570281cc4eec91b55..0000000000000000000000000000000000000000 --- a/python/set_CTBTO_env.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# script to prepare CTBTO environment for -# ECMWFDATA7.0 -# -# Leo Haimberger 1.3.2016 - -export PATH=/dvl/atm/klinkl/software/local/bin:$PATH -export PYTHONPATH=/dvl/atm/klinkl/software/local/lib/python2.7/site-packages/grib_api -export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/dvl/atm/klinkl/software/local/lib/ -export GRIB_API_INCLUDE_DIR=/dvl/atm/klinkl/software/local/include/ -export GRIB_API_LIB='-L/dvl/atm/klinkl/software/local/lib -Bstatic -lgrib_api_f77 -lgrib_api_f90 -lgrib_api -lemosR64 -Bdynamic -lm -ljasper' diff --git a/python/submit.py b/python/submit.py index fcf5735d4693082ac31a587313c8dbd411ff4f58..14cd3caaba0169cb08d5f7c42695d0d78a49601f 100755 --- a/python/submit.py +++ b/python/submit.py @@ -43,17 +43,15 @@ import os import sys import subprocess import inspect +import collections # software specific classes and modules from flex_extract -from tools import interpret_args_and_control, normal_exit +import _config +from tools import normal_exit, get_cmdline_arguments, submit_job_to_ecserver, \ + read_ecenv from get_mars_data import get_mars_data from prepare_flexpart import prepare_flexpart - -# add path to pythonpath so that python finds its buddies -LOCAL_PYTHON_PATH = os.path.dirname(os.path.abspath( - inspect.getfile(inspect.currentframe()))) -if LOCAL_PYTHON_PATH not in sys.path: - sys.path.append(LOCAL_PYTHON_PATH) +from ControlFile import ControlFile # ------------------------------------------------------------------------------ # FUNCTIONS @@ -75,17 +73,36 @@ def main(): ''' called_from_dir = os.getcwd() - args, c = interpret_args_and_control() + + args = get_cmdline_arguments() + + try: + c = ControlFile(args.controlfile) + except IOError: + try: + c = ControlFile(LOCAL_PYTHON_PATH + args.controlfile) + except IOError: + 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' + sys.exit(1) + + env_parameter = read_ecenv(c.ecmwfdatadir + 'python/ECMWF_ENV') + c.assign_args_to_control(args) + c.assign_envs_to_control(env_parameter) + c.check_conditions() # on local side + # on ECMWF server this would be the local side if args.queue is None: if c.inputdir[0] != '/': c.inputdir = os.path.join(called_from_dir, c.inputdir) if c.outputdir[0] != '/': c.outputdir = os.path.join(called_from_dir, c.outputdir) - get_mars_data(args, c) - prepare_flexpart(args, c) - normal_exit(c) + get_mars_data(c) + prepare_flexpart(args.ppid, c) + normal_exit(c.mailfail, 'Done!') # on ECMWF server else: submit(args.job_template, c, args.queue) @@ -124,50 +141,54 @@ def submit(jtemplate, c, queue): <nothing> ''' - # read template file and split from newline signs + # read template file and get index for CONTROL input with open(jtemplate) as f: lftext = f.read().split('\n') - insert_point = lftext.index('EOF') - - # put all parameters of ControlFile instance into a list - clist = c.to_list() # ondemand - colist = [] # operational - mt = 0 - - for elem in clist: - if 'maxstep' in elem: - mt = int(elem.split(' ')[1]) - - for elem in clist: - if 'start_date' in elem: - elem = 'start_date ' + '${MSJ_YEAR}${MSJ_MONTH}${MSJ_DAY}' - if 'end_date' in elem: - elem = 'end_date ' + '${MSJ_YEAR}${MSJ_MONTH}${MSJ_DAY}' - if 'base_time' in elem: - elem = 'base_time ' + '${MSJ_BASETIME}' - if 'time' in elem and mt > 24: - elem = 'time ' + '${MSJ_BASETIME} {MSJ_BASETIME}' - colist.append(elem) - - lftextondemand = lftext[:insert_point] + clist + lftext[insert_point + 2:] - lftextoper = lftext[:insert_point] + colist + lftext[insert_point + 2:] - - with open('job.ksh', 'w') as h: - h.write('\n'.join(lftextondemand)) - - with open('joboper.ksh', 'w') as h: - h.write('\n'.join(lftextoper)) - - # submit job script to queue - try: - p = subprocess.check_call(['ecaccess-job-submit', '-queueName', - queue, 'job.ksh']) - except subprocess.CalledProcessError as e: - print 'ecaccess-job-submit failed!' - print 'Error Message: ' - print e.output - exit(1) + insert_point = lftext.index('EOF') + + if not c.basetime: + # --------- create on demand job script ------------------------------------ + if c.maxstep > 24: + print '---- Pure forecast mode! ----' + else: + print '---- On-demand mode! ----' + job_file = jtemplate[:-4] + 'ksh' + clist = c.to_list() + + lftextondemand = lftext[:insert_point] + clist + lftext[insert_point:] + + with open(job_file, 'w') as f: + f.write('\n'.join(lftextondemand)) + + submit_job_to_ecserver('', queue, job_file) + + else: + # --------- create operational job script ---------------------------------- + print '---- Operational mode! ----' + job_file = jtemplate[:-5] + 'oper.ksh' + #colist = [] + + if c.maxstep: + mt = int(c.maxstep) + else: + mt = 0 + + c.start_date = '${MSJ_YEAR}${MSJ_MONTH}${MSJ_DAY}' + c.end_date = '${MSJ_YEAR}${MSJ_MONTH}${MSJ_DAY}' + c.base_time = '${MSJ_BASETIME}' + if mt > 24: + c.time = '${MSJ_BASETIME} {MSJ_BASETIME}' + + colist = c.to_list() + + lftextoper = lftext[:insert_point] + colist + lftext[insert_point + 2:] + + with open(job_file, 'w') as f: + f.write('\n'.join(lftextoper)) + + submit_job_to_ecserver('', queue, job_file) + # -------------------------------------------------------------------------- print 'You should get an email with subject flex.hostname.pid' return diff --git a/python/test_suite.py b/python/test_suite.py index c20b5f8ccd7afdb90fa33e0a225f45956d72c798..6cd9ed7cfb41cd7faf5b1f5487524535216a889d 100755 --- a/python/test_suite.py +++ b/python/test_suite.py @@ -1,11 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -#************************************************************************ -# ToDo AP -# - provide more tests -# - provide more documentation -#************************************************************************ - #******************************************************************************* # @Author: Leopold Haimberger (University of Vienna) # @@ -24,7 +18,7 @@ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. # # @Program Functionality: -# This script triggers the ECMWFDATA test suite. Call with +# This script triggers the flex_extract test suite. Call with # test_suite.py [test group] # # @Program Content: diff --git a/python/tools.py b/python/tools.py index b74fd685609c04e291849870a0c64f759a79220d..6d9933d07e3b9b1cd901f28aace63317acbd5b14 100644 --- a/python/tools.py +++ b/python/tools.py @@ -1,12 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -#************************************************************************ -# ToDo AP -# - check my_error -# - check normal_exit -# - check get_list_as_string -# - seperate args and control interpretation -#************************************************************************ #******************************************************************************* # @Author: Anne Philipp (University of Vienna) # @@ -25,6 +18,7 @@ # - added documentation # - moved all functions from file Flexparttools to this file tools # - added function get_list_as_string +# - seperated args and control interpretation # # @License: # (C) Copyright 2014-2018. @@ -37,7 +31,7 @@ # used in different places in flex_extract. # # @Module Content: -# - interpret_args_and_control +# - get_cmdline_arguments # - clean_up # - my_error # - normal_exit @@ -46,6 +40,7 @@ # - init128 # - to_param_id # - get_list_as_string +# - make_dir # #******************************************************************************* @@ -56,24 +51,19 @@ import os import errno import sys import glob -import inspect import subprocess import traceback from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter -import numpy as np - -# software specific class from flex_extract -from ControlFile import ControlFile # ------------------------------------------------------------------------------ # FUNCTIONS # ------------------------------------------------------------------------------ -def interpret_args_and_control(): +def get_cmdline_arguments(): ''' @Description: - Assigns the command line arguments and reads CONTROL file - content. Apply default values for non mentioned arguments. + Decomposes the command line arguments and assigns them to variables. + Apply default values for non mentioned arguments. @Input: <nothing> @@ -81,39 +71,28 @@ def interpret_args_and_control(): @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', + ECMWF MARS archive', formatter_class=ArgumentDefaultsHelpFormatter) # the most important arguments - parser.add_argument("--start_date", dest="start_date", + parser.add_argument("--start_date", dest="start_date", default=None, help="start date YYYYMMDD") - parser.add_argument("--end_date", dest="end_date", + parser.add_argument("--end_date", dest="end_date", default=None, help="end_date YYYYMMDD") 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 - parser.add_argument("--basetime", dest="basetime", + parser.add_argument("--basetime", dest="basetime", default=None, help="base such as 00/12 (for half day retrievals)") - parser.add_argument("--step", dest="step", + parser.add_argument("--step", dest="step", default=None, help="steps such as 00/to/48") - parser.add_argument("--levelist", dest="levelist", + parser.add_argument("--levelist", dest="levelist", default=None, help="Vertical levels to be retrieved, e.g. 30/to/60") - parser.add_argument("--area", dest="area", + parser.add_argument("--area", dest="area", default=None, help="area defined as north/west/south/east") # set the working directories @@ -122,124 +101,54 @@ def interpret_args_and_control(): 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", + default=None, help="FLEXPART root directory (to find grib2flexpart \ - and COMMAND file)\n Normally ECMWFDATA resides in \ + and COMMAND file)\n Normally flex_extract resides in \ the scripts directory of the FLEXPART distribution") # this is only used by prepare_flexpart.py to rerun a postprocessing step - parser.add_argument("--ppid", dest="ppid", - help="Specify parent process id for \ + parser.add_argument("--ppid", dest="ppid", default=None, + help="specify parent process id for \ rerun of prepare_flexpart") # arguments for job submission to ECMWF, only needed by submit.py parser.add_argument("--job_template", dest='job_template', default="job.temp", help="job template file for submission to ECMWF") - parser.add_argument("--queue", dest="queue", + parser.add_argument("--queue", dest="queue", default=None, help="queue for submission to ECMWF \ (e.g. ecgate or cca )") parser.add_argument("--controlfile", dest="controlfile", default='CONTROL.temp', help="file with CONTROL parameters") - parser.add_argument("--debug", dest="debug", default=0, - help="Debug mode - leave temporary files intact") + parser.add_argument("--debug", dest="debug", default=None, + help="debug mode - leave temporary files intact") args = parser.parse_args() - # create instance of ControlFile for specified controlfile - # and assign the parameters (and default values if necessary) - try: - c = ControlFile(args.controlfile) - except IOError: - try: - LOCAL_PYTHON_PATH = os.path.dirname(os.path.abspath( - inspect.getfile(inspect.currentframe()))) - c = ControlFile(LOCAL_PYTHON_PATH + args.controlfile) - except IOError: - 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' - exit(1) - - # 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 - print 'Try "' + sys.argv[0].split('/')[-1] + \ - ' -h" to print usage information' - exit(1) - - # 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: - c.start_date = args.start_date - if args.end_date is not None: - c.end_date = args.end_date - if c.end_date is None: - c.end_date = c.start_date - if args.date_chunk is not None: - c.date_chunk = args.date_chunk - - if not hasattr(c, 'debug'): - c.debug = args.debug - - if args.inputdir is None and args.outputdir is None: - c.inputdir = '../work' - c.outputdir = '../work' - else: - if args.inputdir is not None: - c.inputdir = args.inputdir - if args.outputdir is None: - c.outputdir = args.inputdir - if args.outputdir is not None: - c.outputdir = args.outputdir - if args.inputdir is None: - c.inputdir = args.outputdir - - if hasattr(c, 'outputdir') is False and args.outputdir is None: - c.outputdir = c.inputdir - else: - if args.outputdir is not None: - c.outputdir = args.outputdir - - if args.area is not None: - afloat = '.' in args.area - l = args.area.split('/') - if afloat: - for i, item in enumerate(l): - item = str(int(float(item) * 1000)) - c.upper, c.left, c.lower, c.right = l - - # NOTE: basetime activates the ''operational mode'' - if args.basetime is not None: - c.basetime = args.basetime - - if args.step is not None: - l = args.step.split('/') - if 'to' in args.step.lower(): - if 'by' in args.step.lower(): - ilist = np.arange(int(l[0]), int(l[2]) + 1, int(l[4])) - c.step = ['{:0>3}'.format(i) for i in ilist] - else: - my_error(None, args.step + ':\n' + - 'please use "by" as well if "to" is used') - else: - c.step = l + return args - if args.levelist is not None: - c.levelist = args.levelist - if 'to' in c.levelist: - c.level = c.levelist.split('/')[2] - else: - c.level = c.levelist.split('/')[-1] +def read_ecenv(filename): + ''' + @Description: + Reads the file into a dictionary where the key values are the parameter + names. - if args.flexpart_root_scripts is not None: - c.flexpart_root_scripts = args.flexpart_root_scripts + @Input: + filename: string + Name of file where the ECMWV environment parameters are stored. - return args, c + @Return: + envs: dict + ''' + envs= {} + print filename + with open(filename, 'r') as f: + for line in f: + data = line.strip().split() + envs[str(data[0])] = str(data[1]) + return envs def clean_up(c): ''' @@ -268,36 +177,28 @@ def clean_up(c): print "clean_up" cleanlist = glob.glob(c.inputdir + "/*") - for cl in cleanlist: - if c.prefix not in cl: - silent_remove(cl) + for clist in cleanlist: + if c.prefix not in clist: + silent_remove(clist) if c.ecapi is False and (c.ectrans == '1' or c.ecstorage == '1'): - silent_remove(cl) + silent_remove(clist) print "Done" return -def my_error(c, message='ERROR'): +def my_error(users, message='ERROR'): ''' @Description: Prints a specified error message which can be passed to the function before exiting the program. @Input: - 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. + user: list of strings + Contains all email addresses which should be notified. + It might also contain just the ecmwf user name which wil trigger + mailing to the associated email address for this user. message: string, optional Error message. Default value is "ERROR". @@ -309,47 +210,40 @@ def my_error(c, message='ERROR'): print message # comment if user does not want email notification directly from python - try: - target = [] - target.extend(c.mailfail) - except AttributeError: - target = [] - target.extend(os.getenv('USER')) - - for t in target: - p = subprocess.Popen(['mail', '-s ECMWFDATA v7.0 ERROR', - os.path.expandvars(t)], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - bufsize=1) - tr = '\n'.join(traceback.format_stack()) - pout = p.communicate(input=message + '\n\n' + tr)[0] - print 'Email sent to ' + os.path.expandvars(t) + ' ' + pout.decode() - - exit(1) + for user in users: + if '${USER}' in user: + user = os.getenv('USER') + try: + p = subprocess.Popen(['mail', '-s flex_extract_v7.1 ERROR', + os.path.expandvars(user)], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=1) + trace = '\n'.join(traceback.format_stack()) + pout = p.communicate(input=message + '\n\n' + trace)[0] + except ValueError as e: + print 'ERROR: ', e + sys.exit('Email could not be sent!') + else: + print 'Email sent to ' + os.path.expandvars(user) + ' ' + \ + pout.decode() + + sys.exit(1) return -def normal_exit(c, message='Done!'): +def normal_exit(users, message='Done!'): ''' @Description: Prints a specific exit message which can be passed to the function. @Input: - 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. + user: list of strings + Contains all email addresses which should be notified. + It might also contain just the ecmwf user name which wil trigger + mailing to the associated email address for this user. message: string, optional Message for exiting program. Default value is "Done!". @@ -361,20 +255,23 @@ def normal_exit(c, message='Done!'): print message # comment if user does not want notification directly from python - try: - target = [] - target.extend(c.mailops) - for t in target: - p = subprocess.Popen(['mail', '-s ECMWFDATA v7.0 normal exit', - os.path.expandvars(t)], + for user in users: + if '${USER}' in user: + user = os.getenv('USER') + try: + p = subprocess.Popen(['mail', '-s flex_extract_v7.1 normal exit', + os.path.expandvars(user)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1) pout = p.communicate(input=message+'\n\n')[0] - print 'Email sent to ' + os.path.expandvars(t) + ' ' + pout.decode() - finally: - pass + except ValueError as e: + print 'ERROR: ', e + print 'Email could not be sent!' + else: + print 'Email sent to ' + os.path.expandvars(user) + ' ' + \ + pout.decode() return @@ -432,7 +329,7 @@ def silent_remove(filename): os.remove(filename) except OSError as e: # this would be "except OSError, e:" before Python 2.6 - if e.errno is not errno.ENOENT: + if e.errno != errno.ENOENT: # errno.ENOENT = no such file or directory raise # re-raise exception if a different error occured @@ -520,3 +417,97 @@ def get_list_as_string(list_obj, concatenate_sign=', '): str_of_list = concatenate_sign.join(str(l) for l in list_obj) return str_of_list + +def make_dir(directory): + ''' + @Description: + Creates a directory and gives a warning if the directory + already exists. The program stops only if there is another problem. + + @Input: + directory: string + The directory path which should be created. + + @Return: + <nothing> + ''' + try: + os.makedirs(directory) + except OSError as e: + if e.errno != errno.EEXIST: + # errno.EEXIST = directory already exists + raise # re-raise exception if a different error occured + else: + print 'WARNING: Directory {0} already exists!'.format(directory) + + return + +def put_file_to_ecserver(ecd, filename, target, ecuid, ecgid): + ''' + @Description: + Uses the ecaccess command to send a file to the ECMWF servers. + Catches and prints the error if it failed. + + @Input: + ecd: string + The path were the file is to be stored. + + filename: string + The name of the file to send to the ECMWF server. + + target: string + The target where the file should be sent to, e.g. the queue. + + ecuid: string + The user id on ECMWF server. + + ecgid: string + The group id on ECMWF server. + + @Return: + <nothing> + ''' + + try: + subprocess.check_call(['ecaccess-file-put', + ecd + '../' + filename, + target + ':/home/ms/' + + ecgid + '/' + ecuid + + '/' + filename]) + except subprocess.CalledProcessError as e: + print 'ERROR:' + print e + sys.exit('ecaccess-file-put failed!\n' + \ + 'Probably the eccert key has expired.') + + return + +def submit_job_to_ecserver(ecd, target, jobname): + ''' + @Description: + Uses ecaccess to submit a job to the ECMWF server. + Catches and prints the error if one arise. + + @Input: + ecd: string + The path were the file is to be stored. + + target: string + The target where the file should be sent to, e.g. the queue. + + jobname: string + The name of the jobfile to be submitted to the ECMWF server. + + @Return: + <nothing> + ''' + + try: + subprocess.check_call(['ecaccess-job-submit', + '-queueName', target, + jobname]) + except subprocess.CalledProcessError as e: + print '... ERROR CODE: ', e.returncode + sys.exit('... ECACCESS-JOB-SUBMIT FAILED!') + + return diff --git a/pythontest/TestTools.py b/pythontest/TestTools.py index 5fe4feb1532564c8a82290b343dc92d365727ebd..ee0d97034ee7f2419ba25f5080f24144d8f920a3 100644 --- a/pythontest/TestTools.py +++ b/pythontest/TestTools.py @@ -3,8 +3,9 @@ import unittest import sys +import os sys.path.append('../python') -from Tools import init128, toparamId +from tools import init128, to_param_id, my_error, read_ecenv class TestTools(unittest.TestCase): @@ -24,14 +25,35 @@ class TestTools(unittest.TestCase): self.assertEqual(result, True) - def test_toparamId(self): + def test_to_param_id(self): ''' ''' table128 = init128('../grib_templates/ecmwf_grib1_table_128') - pars = toparamId("T/SP/LSP/SSHF", table128) + pars = to_param_id("T/SP/LSP/SSHF", table128) for par in pars: self.assertIn(par, [130, 134, 142, 146]) + def test_error_notifcation(self): + ''' + ''' + with self.assertRaises(SystemExit) as re: + my_error(['${USER}', 'anne.philipp@univie.ac.at'], 'Failed!') + self.assertEqual(re.exception.code, 1) + + def test_read_ecenv(self): + + envs_ref = {'ECUID': 'km4a', + 'ECGID': 'at', + 'GATEWAY': 'srvx8.img.univie.ac.at', + 'DESTINATION': 'annep@genericSftp' + } + envs = read_ecenv(os.getcwd() + '/TestData/ECMWF_ENV') + + self.assertDictEqual(envs_ref, envs) + + + + if __name__ == "__main__": unittest.main() \ No newline at end of file diff --git a/pythontest/TestUIOFiles.py b/pythontest/TestUIOFiles.py index 3d929cb3d096fde8ce03d25c9def2bed4537c130..d60cbf040c3a7223502190568824040b95a03c53 100644 --- a/pythontest/TestUIOFiles.py +++ b/pythontest/TestUIOFiles.py @@ -5,10 +5,10 @@ import unittest import os import sys sys.path.append('../python') -import UIOFiles +import UioFiles -class TestUIOFiles(unittest.TestCase): +class TestUioFiles(unittest.TestCase): ''' Test class to test the UIOFiles methods. ''' @@ -51,8 +51,8 @@ class TestUIOFiles(unittest.TestCase): ''' # Initialise and collect filenames - files = UIOFiles.UIOFiles(['.grb']) - files.listFiles(self.testpath, '*') + files = UioFiles.UioFiles(['.grb']) + files.list_files(self.testpath, '*') # get the basename to just check for equality of filenames filelist = [os.path.basename(f) for f in files.files] # comparison of expected filenames against the collected ones