source: flex_extract.git/source/python/classes/ControlFile.py @ 5bad6ec

ctbtodev
Last change on this file since 5bad6ec was 5bad6ec, checked in by Anne Philipp <anne.philipp@…>, 6 years ago

added possibility to extract public datasets via an logical public parameter

  • Property mode set to 100644
File size: 19.4 KB
RevLine 
[812283d]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#*******************************************************************************
4# @Author: Leopold Haimberger (University of Vienna)
5#
6# @Date: November 2015
7#
8# @Change History:
9#
10#   February 2018 - Anne Philipp (University of Vienna):
11#        - applied PEP8 style guide
12#        - added documentation
13#        - applied some minor modifications in programming style/structure
14#        - changed name of class Control to ControlFile for more
15#          self-explanation naming
16#        - outsource of class ControlFile
[54a8a01]17#        - initialisation of class attributes ( to avoid high number of
18#          conditional statements and set default values )
19#        - divided assignment of attributes and the check of conditions
20#        - outsourced the commandline argument assignments to control attributes
[812283d]21#
22# @License:
23#    (C) Copyright 2015-2018.
24#
25#    This software is licensed under the terms of the Apache Licence Version 2.0
26#    which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
27#
28# @Class Description:
29#    The CONTROL file is the steering part of the FLEXPART extraction
30#    software. All necessary parameters needed to retrieve the data fields
31#    from the MARS archive for driving FLEXPART are set in a CONTROL file.
32#    Some specific parameters like the start and end dates can be overwritten
33#    by the command line parameters, but in generel all parameters needed
34#    for a complete set of fields for FLEXPART can be set in the CONTROL file.
35#
36# @Class Content:
37#    - __init__
[54a8a01]38#    - __read_controlfile__
[812283d]39#    - __str__
[54a8a01]40#    - assign_args_to_control
41#    - assign_envs_to_control
42#    - check_conditions
43#    - check_install_conditions
[ff99eae]44#    - to_list
45#
46# @Class Attributes:
[54a8a01]47#
[812283d]48#
49#*******************************************************************************
50
51# ------------------------------------------------------------------------------
52# MODULES
53# ------------------------------------------------------------------------------
54import os
[2fb99de]55import re
[54a8a01]56import sys
[812283d]57import inspect
[ff99eae]58
[ca867de]59# software specific classes and modules from flex_extract
60sys.path.append('../')
[2fb99de]61import _config
[ca867de]62from mods.tools import my_error
[2fb99de]63
[812283d]64# ------------------------------------------------------------------------------
65# CLASS
66# ------------------------------------------------------------------------------
[ff99eae]67class ControlFile(object):
[812283d]68    '''
69    Class containing the information of the flex_extract CONTROL file.
70
71    Contains all the parameters of CONTROL file, which are e.g.:
72    DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME,
73    STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT,
74    LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY,
75    OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, SMOOTH, FORMAT,
76    ADDPAR, WRF, CWC, PREFIX, ECSTORAGE, ECTRANS, ECFSDIR,
77    MAILOPS, MAILFAIL, GRIB2FLEXPART, FLEXPARTDIR,
78    BASETIME, DATE_CHUNK, DEBUG, INPUTDIR, OUTPUTDIR, FLEXPART_ROOT_SCRIPTS
79
80    For more information about format and content of the parameter
81    see documentation.
82
83    '''
84
85    def __init__(self, filename):
86        '''
87        @Description:
[54a8a01]88            Initialises the instance of ControlFile class and defines
89            all class attributes with default values. Afterwards calls
90            function __read_controlfile__ to read parameter from
91            Control file.
[812283d]92
93        @Input:
94            self: instance of ControlFile class
95                Description see class documentation.
96
97            filename: string
98                Name of CONTROL file.
99
100        @Return:
101            <nothing>
102        '''
[54a8a01]103
104        # list of all possible class attributes and their default values
105        self.controlfile = filename
106        self.start_date = None
107        self.end_date = None
108        self.date_chunk = 3
109        self.dtime = None
110        self.basetime = None
111        self.maxstep = None
112        self.type = None
113        self.time = None
114        self.step = None
115        self.marsclass = None
[5bad6ec]116        self.dataset = None
[54a8a01]117        self.stream = None
118        self.number = 'OFF'
[2fb99de]119        self.expver = '1'
[54a8a01]120        self.grid = None
121        self.area = ''
122        self.left = None
123        self.lower = None
124        self.upper = None
125        self.right = None
126        self.level = None
127        self.levelist = None
128        self.resol = None
129        self.gauss = 0
130        self.accuracy = 24
131        self.omega = 0
132        self.omegadiff = 0
133        self.eta = 0
134        self.etadiff = 0
135        self.etapar = 77
136        self.dpdeta = 1
137        self.smooth = 0
138        self.format = 'GRIB1'
139        self.addpar = None
140        self.prefix = 'EN'
141        self.cwc = 0
142        self.wrf = 0
143        self.ecfsdir = 'ectmp:/${USER}/econdemand/'
144        self.mailfail = ['${USER}']
145        self.mailops = ['${USER}']
146        self.grib2flexpart = 0
147        self.ecstorage = 0
148        self.ectrans = 0
[25b14be]149        self.inputdir = _config.PATH_INPUT_DIR
[ca867de]150        self.outputdir = None
[2fb99de]151        self.ecmwfdatadir = _config.PATH_FLEXEXTRACT_DIR
152        self.exedir = _config.PATH_FORTRAN_SRC
[54a8a01]153        self.flexpart_root_scripts = None
[2fb99de]154        self.makefile = 'Makefile.gfortran'
[54a8a01]155        self.destination = None
156        self.gateway = None
157        self.ecuid = None
158        self.ecgid = None
159        self.install_target = None
160        self.debug = 0
[2fb99de]161        self.request = 0
[5bad6ec]162        self.public = 0
[2fb99de]163
164        self.logicals = ['gauss', 'omega', 'omegadiff', 'eta', 'etadiff',
165                         'dpdeta', 'cwc', 'wrf', 'grib2flexpart', 'ecstorage',
[5bad6ec]166                         'ectrans', 'debug', 'request', 'public']
[54a8a01]167
168        self.__read_controlfile__()
169
170        return
171
172    def __read_controlfile__(self):
173        '''
174        @Description:
175            Read CONTROL file and assign all CONTROL file variables.
176
177        @Input:
178            self: instance of ControlFile class
179                Description see class documentation.
180
181        @Return:
182            <nothing>
183        '''
[4971f63]184
185        try:
186            with open(os.path.join(_config.PATH_CONTROLFILES,
187                                   self.controlfile)) as f:
188                fdata = f.read().split('\n')
189        except IOError:
190            print('Could not read CONTROL file "' + args.controlfile + '"')
191            print('Either it does not exist or its syntax is wrong.')
192            print('Try "' + sys.argv[0].split('/')[-1] + \
193                      ' -h" to print usage information')
194            sys.exit(1)
[812283d]195
196        # go through every line and store parameter
197        for ldata in fdata:
198            data = ldata.split()
199            if len(data) > 1:
200                if 'm_' in data[0].lower():
201                    data[0] = data[0][2:]
202                if data[0].lower() == 'class':
203                    data[0] = 'marsclass'
204                if data[0].lower() == 'day1':
205                    data[0] = 'start_date'
206                if data[0].lower() == 'day2':
207                    data[0] = 'end_date'
208                if data[0].lower() == 'addpar':
209                    if '/' in data[1]:
210                        # remove leading '/' sign from addpar content
211                        if data[1][0] == '/':
212                            data[1] = data[1][1:]
213                        dd = data[1].split('/')
214                        data = [data[0]]
215                        for d in dd:
216                            data.append(d)
217                if len(data) == 2:
218                    if '$' in data[1]:
219                        setattr(self, data[0].lower(), data[1])
220                        while '$' in data[1]:
221                            i = data[1].index('$')
222                            j = data[1].find('{')
223                            k = data[1].find('}')
224                            var = os.getenv(data[1][j+1:k])
225                            if var is not None:
226                                data[1] = data[1][:i] + var + data[1][k+1:]
227                            else:
[54a8a01]228                                my_error(self.mailfail,
229                                         'Could not find variable '
230                                         + data[1][j+1:k] + ' while reading ' +
231                                         self.controlfile)
[812283d]232                        setattr(self, data[0].lower() + '_expanded', data[1])
233                    else:
234                        if data[1].lower() != 'none':
235                            setattr(self, data[0].lower(), data[1])
236                        else:
237                            setattr(self, data[0].lower(), None)
238                elif len(data) > 2:
239                    setattr(self, data[0].lower(), (data[1:]))
240            else:
241                pass
242
[54a8a01]243        return
244
245    def __str__(self):
246        '''
247        @Description:
248            Prepares a string which have all the ControlFile
249            class attributes with its associated values.
250            Each attribute is printed in one line and in
251            alphabetical order.
252
253            Example:
254            'age': 10
255            'color': 'Spotted'
256            'kids': 0
257            'legs': 2
258            'name': 'Dog'
259            'smell': 'Alot'
260
261        @Input:
262            self: instance of ControlFile class
263                Description see class documentation.
264
265        @Return:
266            string of ControlFile class attributes with their values
267        '''
268        import collections
269
[5bad6ec]270        attrs = vars(self).copy()
[54a8a01]271        attrs = collections.OrderedDict(sorted(attrs.items()))
272
273        return '\n'.join("%s: %s" % item for item in attrs.items())
274
275    def assign_args_to_control(self, args):
276        '''
277        @Description:
278            Overwrites the existing ControlFile instance attributes with
279            the command line arguments.
280
281        @Input:
282            self: instance of ControlFile class
283                Description see class documentation.
284
285            args: instance of ArgumentParser
286                Contains the commandline arguments from script/program call.
287
288        @Return:
289            <nothing>
290        '''
291
292        # get dictionary of command line parameters and eliminate all
293        # parameters which are None (were not specified)
294        args_dict = vars(args)
295        arguments = {k : args_dict[k] for k in args_dict
296                     if args_dict[k] != None}
297
298        # assign all passed command line arguments to ControlFile instance
299        for k, v in arguments.iteritems():
300            setattr(self, str(k), v)
301
302        return
303
304    def assign_envs_to_control(self, envs):
305        '''
306        @Description:
307            Assigns the ECMWF environment parameter.
308
309        @Input:
310            envs: dict of strings
311                Contains the ECMWF environment parameternames "ECUID", "ECGID",
312                "DESTINATION" and "GATEWAY" with its corresponding values.
313                They were read from the file "ECMWF_ENV".
314
315        @Return:
316            <nothing>
317        '''
318
319        for k, v in envs.iteritems():
320            setattr(self, str(k).lower(), str(v))
321
322        return
323
[2fb99de]324    def check_conditions(self, queue):
[54a8a01]325        '''
326        @Description:
327            Checks a couple of necessary attributes and conditions,
328            such as if they exist and contain values.
329            Otherwise set default values.
330
331        @Input:
332            self: instance of ControlFile class
333                Description see class documentation.
334
[2fb99de]335            queue: string
336                Name of the queue if submitted to the ECMWF servers.
337                Used to check if ecuid, ecgid, gateway and destination
338                are set correctly and are not empty.
339
[54a8a01]340        @Return:
341            <nothing>
342        '''
[25b14be]343        from mods.tools import my_error
[54a8a01]344        import numpy as np
345
346        # check for having at least a starting date
347        # otherwise program is not allowed to run
348        if self.start_date is None:
[2fb99de]349            print('start_date specified neither in command line nor \
350                   in CONTROL file ' +  self.controlfile)
351            print('Try "' + sys.argv[0].split('/')[-1] +
352                  ' -h" to print usage information')
[54a8a01]353            sys.exit(1)
354
355        # retrieve just one day if end_date isn't set
356        if self.end_date is None:
[812283d]357            self.end_date = self.start_date
[54a8a01]358
[ca867de]359        # basetime has only two possible values
360        if self.basetime:
361            if int(self.basetime) != 0 and int(self.basetime) != 12:
362                print('Basetime has an invalid value!')
363                print('Basetime = ' + str(self.basetime))
364                sys.exit(1)
365
[54a8a01]366        # assure consistency of levelist and level
[2fb99de]367        if self.levelist is None and self.level is None:
368            print('Warning: neither levelist nor level \
369                               specified in CONTROL file')
370            sys.exit(1)
371        elif self.levelist is None and self.level:
372            self.levelist = '1/to/' + self.level
373        elif (self.levelist and self.level is None) or \
374             (self.levelist[-1] != self.level[-1]):
375            self.level = self.levelist.split('/')[-1]
[812283d]376        else:
[2fb99de]377            pass
[812283d]378
[2fb99de]379        # if area was provided (only from commandline)
380        # decompose area into its 4 components
[54a8a01]381        if self.area:
[2fb99de]382            components = self.area.split('/')
383            # convert float to integer coordinates
384            if '.' in self.area:
385                components = [str(int(float(item) * 1000))
386                              for i, item in enumerate(components)]
387            self.upper, self.left, self.lower, self.right = components
388
389        # prepare step list if "/" signs are found
[54a8a01]390        if '/' in self.step:
[2fb99de]391            steps = self.step.split('/')
392            if 'to' in self.step.lower() and 'by' in self.step.lower():
393                ilist = np.arange(int(steps[0]),
394                                  int(steps[2]) + 1,
395                                  int(steps[4]))
396                self.step = ['{:0>3}'.format(i) for i in ilist]
397            elif 'to' in self.step.lower() and 'by' not in self.step.lower():
398                my_error(self.mailfail, self.step + ':\n' +
399                         'if "to" is used in steps parameter, \
400                         please use "by" as well')
[54a8a01]401            else:
[2fb99de]402                self.step = steps
[54a8a01]403
404        # if maxstep wasn't provided
405        # search for it in the "step" parameter
406        if self.maxstep is None:
[812283d]407            self.maxstep = 0
408            for s in self.step:
409                if int(s) > self.maxstep:
410                    self.maxstep = int(s)
411        else:
412            self.maxstep = int(self.maxstep)
413
[54a8a01]414        # set root scripts since it is needed later on
415        if not self.flexpart_root_scripts:
416            self.flexpart_root_scripts = self.ecmwfdatadir
[812283d]417
[ca867de]418        if not self.outputdir:
419            self.outputdir = self.inputdir
420
[54a8a01]421        if not isinstance(self.mailfail, list):
422            if ',' in self.mailfail:
423                self.mailfail = self.mailfail.split(',')
424            elif ' ' in self.mailfail:
425                self.mailfail = self.mailfail.split()
426            else:
427                self.mailfail = [self.mailfail]
[812283d]428
[54a8a01]429        if not isinstance(self.mailops, list):
430            if ',' in self.mailops:
431                self.mailops = self.mailops.split(',')
432            elif ' ' in self.mailops:
433                self.mailops = self.mailops.split()
434            else:
435                self.mailops = [self.mailops]
436
[2fb99de]437        if queue in ['ecgate', 'cca'] and \
438           not self.gateway or not self.destination or \
[54a8a01]439           not self.ecuid or not self.ecgid:
[2fb99de]440            print('\nEnvironment variables GATEWAY, DESTINATION, ECUID and \
441                   ECGID were not set properly!')
442            print('Please check for existence of file "ECMWF_ENV" in the \
443                   python directory!')
[54a8a01]444            sys.exit(1)
[812283d]445
[2fb99de]446        if self.request != 0:
[5d42acd]447            marsfile = os.path.join(self.inputdir,
[2fb99de]448                                    _config.FILE_MARS_REQUESTS)
449            if os.path.isfile(marsfile):
450                os.remove(marsfile)
451
452        # check all logical variables for data type
453        # if its a string change to integer
454        for var in self.logicals:
455            if not isinstance(getattr(self, var), int):
456                setattr(self, var, int(getattr(self, var)))
457
[5bad6ec]458        if self.public and not self.dataset:
459            print('ERROR: ')
460            print('If public mars data wants to be retrieved, '
461                  'the "dataset"-parameter has to be set in the control file!')
462            sys.exit(1)
463
[812283d]464        return
465
[54a8a01]466    def check_install_conditions(self):
[812283d]467        '''
468        @Description:
[54a8a01]469            Checks a couple of necessary attributes and conditions
470            for the installation such as if they exist and contain values.
471            Otherwise set default values.
[812283d]472
473        @Input:
474            self: instance of ControlFile class
475                Description see class documentation.
476
477        @Return:
[54a8a01]478            <nothing>
[812283d]479        '''
480
[54a8a01]481        if self.install_target and \
482           self.install_target not in ['local', 'ecgate', 'cca']:
[2fb99de]483            print('ERROR: unknown or missing installation target ')
484            print('target: ', self.install_target)
485            print('please specify correct installation target ' +
486                  '(local | ecgate | cca)')
487            print('use -h or --help for help')
[54a8a01]488            sys.exit(1)
489
490        if self.install_target and self.install_target != 'local':
491            if not self.ecgid or not self.ecuid or \
492               not self.gateway or not self.destination:
[2fb99de]493                print('Please enter your ECMWF user id and group id as well ' +
494                      'as the \nname of the local gateway and the ectrans ' +
495                      'destination ')
496                print('with command line options --ecuid --ecgid \
497                       --gateway --destination')
498                print('Try "' + sys.argv[0].split('/')[-1] + \
499                      ' -h" to print usage information')
500                print('Please consult ecaccess documentation or ECMWF user \
501                       support for further details')
[54a8a01]502                sys.exit(1)
503
504            if not self.flexpart_root_scripts:
505                self.flexpart_root_scripts = '${HOME}'
506            else:
507                self.flexpart_root_scripts = self.flexpart_root_scripts
[2fb99de]508        else: # local
[54a8a01]509            if not self.flexpart_root_scripts:
[2fb99de]510                self.flexpart_root_scripts = _config.PATH_FLEXEXTRACT_DIR
[54a8a01]511
512        return
[812283d]513
[ff99eae]514    def to_list(self):
[812283d]515        '''
516        @Description:
517            Just generates a list of strings containing the attributes and
518            assigned values except the attributes "_expanded", "exedir",
519            "ecmwfdatadir" and "flexpart_root_scripts".
520
521        @Input:
522            self: instance of ControlFile class
523                Description see class documentation.
524
525        @Return:
526            l: list
527                A sorted list of the all ControlFile class attributes with
528                their values except the attributes "_expanded", "exedir",
529                "ecmwfdatadir" and "flexpart_root_scripts".
530        '''
531
[54a8a01]532        import collections
533
[5bad6ec]534        attrs = collections.OrderedDict(sorted(vars(self).copy().items()))
[54a8a01]535
[812283d]536        l = list()
537
538        for item in attrs.items():
539            if '_expanded' in item[0]:
540                pass
541            elif 'exedir' in item[0]:
542                pass
543            elif 'flexpart_root_scripts' in item[0]:
544                pass
545            elif 'ecmwfdatadir' in item[0]:
546                pass
547            else:
[ff99eae]548                if isinstance(item[1], list):
[812283d]549                    stot = ''
550                    for s in item[1]:
551                        stot += s + ' '
552
553                    l.append("%s %s" % (item[0], stot))
554                else:
555                    l.append("%s %s" % item)
556
557        return sorted(l)
[ff99eae]558
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG