source: flex_extract.git/source/python/classes/ControlFile.py @ 0e576fc

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

implemented extraction possibility of EA5 und CERA

  • Property mode set to 100644
File size: 21.2 KB
Line 
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
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
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__
38#    - __read_controlfile__
39#    - __str__
40#    - assign_args_to_control
41#    - assign_envs_to_control
42#    - check_conditions
43#    - check_install_conditions
44#    - to_list
45#
46# @Class Attributes:
47#
48#
49#*******************************************************************************
50
51# ------------------------------------------------------------------------------
52# MODULES
53# ------------------------------------------------------------------------------
54import os
55import re
56import sys
57import inspect
58
59# software specific classes and modules from flex_extract
60sys.path.append('../')
61import _config
62from mods.tools import my_error
63
64# ------------------------------------------------------------------------------
65# CLASS
66# ------------------------------------------------------------------------------
67class ControlFile(object):
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:
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.
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        '''
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.acctype = None
116        self.acctime = None
117        self.accmaxstep = None
118        self.marsclass = None
119        self.dataset = None
120        self.stream = None
121        self.number = 'OFF'
122        self.expver = '1'
123        self.gaussian = ''
124        self.grid = None
125        self.area = ''
126        self.left = None
127        self.lower = None
128        self.upper = None
129        self.right = None
130        self.level = None
131        self.levelist = None
132        self.resol = None
133        self.gauss = 0
134        self.accuracy = 24
135        self.omega = 0
136        self.omegadiff = 0
137        self.eta = 0
138        self.etadiff = 0
139        self.etapar = 77
140        self.dpdeta = 1
141        self.smooth = 0
142        self.format = 'GRIB1'
143        self.addpar = None
144        self.prefix = 'EN'
145        self.cwc = 0
146        self.wrf = 0
147        self.ecfsdir = 'ectmp:/${USER}/econdemand/'
148        self.mailfail = ['${USER}']
149        self.mailops = ['${USER}']
150        self.grib2flexpart = 0
151        self.ecstorage = 0
152        self.ectrans = 0
153        self.inputdir = _config.PATH_INPUT_DIR
154        self.outputdir = None
155        self.ecmwfdatadir = _config.PATH_FLEXEXTRACT_DIR
156        self.exedir = _config.PATH_FORTRAN_SRC
157        self.flexpart_root_scripts = None
158        self.makefile = 'Makefile.gfortran'
159        self.destination = None
160        self.gateway = None
161        self.ecuid = None
162        self.ecgid = None
163        self.install_target = None
164        self.debug = 0
165        self.request = 0
166        self.public = 0
167
168        self.logicals = ['gauss', 'omega', 'omegadiff', 'eta', 'etadiff',
169                         'dpdeta', 'cwc', 'wrf', 'grib2flexpart', 'ecstorage',
170                         'ectrans', 'debug', 'request', 'public']
171
172        self.__read_controlfile__()
173
174        return
175
176    def __read_controlfile__(self):
177        '''
178        @Description:
179            Read CONTROL file and assign all CONTROL file variables.
180
181        @Input:
182            self: instance of ControlFile class
183                Description see class documentation.
184
185        @Return:
186            <nothing>
187        '''
188
189        try:
190            with open(os.path.join(_config.PATH_CONTROLFILES,
191                                   self.controlfile)) as f:
192                fdata = f.read().split('\n')
193        except IOError:
194            print('Could not read CONTROL file "' + args.controlfile + '"')
195            print('Either it does not exist or its syntax is wrong.')
196            print('Try "' + sys.argv[0].split('/')[-1] + \
197                      ' -h" to print usage information')
198            sys.exit(1)
199
200        # go through every line and store parameter
201        for ldata in fdata:
202            data = ldata.split()
203            if len(data) > 1:
204                if 'm_' in data[0].lower():
205                    data[0] = data[0][2:]
206                if data[0].lower() == 'class':
207                    data[0] = 'marsclass'
208                if data[0].lower() == 'day1':
209                    data[0] = 'start_date'
210                if data[0].lower() == 'day2':
211                    data[0] = 'end_date'
212                if data[0].lower() == 'addpar':
213                    if '/' in data[1]:
214                        # remove leading '/' sign from addpar content
215                        if data[1][0] == '/':
216                            data[1] = data[1][1:]
217                        dd = data[1].split('/')
218                        data = [data[0]]
219                        for d in dd:
220                            data.append(d)
221                if len(data) == 2:
222                    if '$' in data[1]:
223                        setattr(self, data[0].lower(), data[1])
224                        while '$' in data[1]:
225                            i = data[1].index('$')
226                            j = data[1].find('{')
227                            k = data[1].find('}')
228                            var = os.getenv(data[1][j+1:k])
229                            if var is not None:
230                                data[1] = data[1][:i] + var + data[1][k+1:]
231                            else:
232                                my_error(self.mailfail,
233                                         'Could not find variable '
234                                         + data[1][j+1:k] + ' while reading ' +
235                                         self.controlfile)
236                        setattr(self, data[0].lower() + '_expanded', data[1])
237                    else:
238                        if data[1].lower() != 'none':
239                            setattr(self, data[0].lower(), data[1])
240                        else:
241                            setattr(self, data[0].lower(), None)
242                elif len(data) > 2:
243                    setattr(self, data[0].lower(), (data[1:]))
244            else:
245                pass
246
247        return
248
249    def __str__(self):
250        '''
251        @Description:
252            Prepares a string which have all the ControlFile
253            class attributes with its associated values.
254            Each attribute is printed in one line and in
255            alphabetical order.
256
257            Example:
258            'age': 10
259            'color': 'Spotted'
260            'kids': 0
261            'legs': 2
262            'name': 'Dog'
263            'smell': 'Alot'
264
265        @Input:
266            self: instance of ControlFile class
267                Description see class documentation.
268
269        @Return:
270            string of ControlFile class attributes with their values
271        '''
272        import collections
273
274        attrs = vars(self).copy()
275        attrs = collections.OrderedDict(sorted(attrs.items()))
276
277        return '\n'.join("%s: %s" % item for item in attrs.items())
278
279    def assign_args_to_control(self, args):
280        '''
281        @Description:
282            Overwrites the existing ControlFile instance attributes with
283            the command line arguments.
284
285        @Input:
286            self: instance of ControlFile class
287                Description see class documentation.
288
289            args: instance of ArgumentParser
290                Contains the commandline arguments from script/program call.
291
292        @Return:
293            <nothing>
294        '''
295
296        # get dictionary of command line parameters and eliminate all
297        # parameters which are None (were not specified)
298        args_dict = vars(args)
299        arguments = {k : args_dict[k] for k in args_dict
300                     if args_dict[k] != None}
301
302        # assign all passed command line arguments to ControlFile instance
303        for k, v in arguments.iteritems():
304            setattr(self, str(k), v)
305
306        return
307
308    def assign_envs_to_control(self, envs):
309        '''
310        @Description:
311            Assigns the ECMWF environment parameter.
312
313        @Input:
314            envs: dict of strings
315                Contains the ECMWF environment parameternames "ECUID", "ECGID",
316                "DESTINATION" and "GATEWAY" with its corresponding values.
317                They were read from the file "ECMWF_ENV".
318
319        @Return:
320            <nothing>
321        '''
322
323        for k, v in envs.iteritems():
324            setattr(self, str(k).lower(), str(v))
325
326        return
327
328    def check_conditions(self, queue):
329        '''
330        @Description:
331            Checks a couple of necessary attributes and conditions,
332            such as if they exist and contain values.
333            Otherwise set default values.
334
335        @Input:
336            self: instance of ControlFile class
337                Description see class documentation.
338
339            queue: string
340                Name of the queue if submitted to the ECMWF servers.
341                Used to check if ecuid, ecgid, gateway and destination
342                are set correctly and are not empty.
343
344        @Return:
345            <nothing>
346        '''
347        from mods.tools import my_error
348        import numpy as np
349
350        # check for having at least a starting date
351        # otherwise program is not allowed to run
352        if not self.start_date:
353            print('start_date specified neither in command line nor \
354                   in CONTROL file ' +  self.controlfile)
355            print('Try "' + sys.argv[0].split('/')[-1] +
356                  ' -h" to print usage information')
357            sys.exit(1)
358
359        # retrieve just one day if end_date isn't set
360        if not self.end_date:
361            self.end_date = self.start_date
362
363        # basetime has only two possible values
364        if self.basetime:
365            if int(self.basetime) != 0 and int(self.basetime) != 12:
366                print('Basetime has an invalid value!')
367                print('Basetime = ' + str(self.basetime))
368                sys.exit(1)
369
370        # assure consistency of levelist and level
371        # up-to-date available maximum level numbers at ECMWF, 05.10.2018
372        max_level_list = [16, 19, 31, 40, 50, 60, 62, 91, 137]
373        if not self.levelist and not self.level:
374            print('Warning: neither levelist nor level \
375                               specified in CONTROL file')
376            sys.exit(1)
377        elif not self.levelist and self.level:
378            self.levelist = '1/to/' + self.level
379        elif (self.levelist and not self.level) or \
380             (self.levelist[-1] != self.level[-1]):
381            self.level = self.levelist.split('/')[-1]
382        else:
383            pass
384
385        # check if max level is a valid level
386        if int(self.level) not in max_level_list:
387            print('ERROR: ')
388            print('LEVEL must be the maximum level of a specified '
389                  'level list from ECMWF, e.g.')
390            print('[16, 19, 31, 40, 50, 60, 62, 91 or 137]')
391            print('Check parameter "LEVEL" or the max level of "LEVELIST"!')
392            sys.exit(1)
393
394        # if area was provided (only from commandline)
395        # decompose area into its 4 components
396        if self.area:
397            components = self.area.split('/')
398            # convert float to integer coordinates
399            if '.' in self.area:
400                components = [str(int(float(item) * 1000))
401                              for i, item in enumerate(components)]
402            self.upper, self.left, self.lower, self.right = components
403
404        # prepare step list if "/" signs are found
405        if '/' in self.step:
406            steps = self.step.split('/')
407            if 'to' in self.step.lower() and 'by' in self.step.lower():
408                ilist = np.arange(int(steps[0]),
409                                  int(steps[2]) + 1,
410                                  int(steps[4]))
411                self.step = ['{:0>3}'.format(i) for i in ilist]
412            elif 'to' in self.step.lower() and 'by' not in self.step.lower():
413                my_error(self.mailfail, self.step + ':\n' +
414                         'if "to" is used in steps parameter, \
415                         please use "by" as well')
416            else:
417                self.step = steps
418
419        # if maxstep wasn't provided
420        # search for it in the "step" parameter
421        if not self.maxstep:
422            self.maxstep = 0
423            for s in self.step:
424                if int(s) > self.maxstep:
425                    self.maxstep = int(s)
426        else:
427            self.maxstep = int(self.maxstep)
428
429        # set root scripts since it is needed later on
430        if not self.flexpart_root_scripts:
431            self.flexpart_root_scripts = self.ecmwfdatadir
432
433        if not self.outputdir:
434            self.outputdir = self.inputdir
435
436        if not isinstance(self.mailfail, list):
437            if ',' in self.mailfail:
438                self.mailfail = self.mailfail.split(',')
439            elif ' ' in self.mailfail:
440                self.mailfail = self.mailfail.split()
441            else:
442                self.mailfail = [self.mailfail]
443
444        if not isinstance(self.mailops, list):
445            if ',' in self.mailops:
446                self.mailops = self.mailops.split(',')
447            elif ' ' in self.mailops:
448                self.mailops = self.mailops.split()
449            else:
450                self.mailops = [self.mailops]
451
452        if queue in ['ecgate', 'cca'] and \
453           not self.gateway or not self.destination or \
454           not self.ecuid or not self.ecgid:
455            print('\nEnvironment variables GATEWAY, DESTINATION, ECUID and \
456                   ECGID were not set properly!')
457            print('Please check for existence of file "ECMWF_ENV" in the \
458                   python directory!')
459            sys.exit(1)
460
461        if self.request != 0:
462            marsfile = os.path.join(self.inputdir,
463                                    _config.FILE_MARS_REQUESTS)
464            if os.path.isfile(marsfile):
465                os.remove(marsfile)
466
467        # check all logical variables for data type
468        # if its a string change to integer
469        for var in self.logicals:
470            if not isinstance(getattr(self, var), int):
471                setattr(self, var, int(getattr(self, var)))
472
473        if self.public and not self.dataset:
474            print('ERROR: ')
475            print('If public mars data wants to be retrieved, '
476                  'the "dataset"-parameter has to be set in the control file!')
477            sys.exit(1)
478
479        if not isinstance(self.type, list):
480            self.type = [self.type]
481
482        for i, val in enumerate(self.type):
483            if self.type[i] == 'AN' and int(self.step[i]) != 0:
484                print('Analysis retrievals must have STEP = 0 (is set to 0)')
485                self.type[i] = 0
486
487        if not isinstance(self.time, list):
488            self.time = [self.time]
489
490        if not isinstance(self.step, list):
491            self.step = [self.step]
492
493        if not self.acctype:
494            print('... Control paramter ACCTYPE was not defined.')
495            try:
496                if len(self.type) > 1 and self.type[1] != 'AN':
497                    print('Use old setting by using TYPE[1] for flux forecast!')
498                    self.acctype = self.type[1]
499            except:
500                print('Use default value "FC" for flux forecast!')
501                self.acctype='FC'
502
503        if not self.acctime:
504            print('... Control paramter ACCTIME was not defined.')
505            print('Use default value "00/12" for flux forecast!')
506            self.acctime='00/12'
507
508        if not self.accmaxstep:
509            print('... Control paramter ACCMAXSTEP was not defined.')
510            print('Use default value "12" for flux forecast!')
511            self.accmaxstep='12'
512
513        return
514
515    def check_install_conditions(self):
516        '''
517        @Description:
518            Checks a couple of necessary attributes and conditions
519            for the installation such as if they exist and contain values.
520            Otherwise set default values.
521
522        @Input:
523            self: instance of ControlFile class
524                Description see class documentation.
525
526        @Return:
527            <nothing>
528        '''
529
530        if self.install_target and \
531           self.install_target not in ['local', 'ecgate', 'cca']:
532            print('ERROR: unknown or missing installation target ')
533            print('target: ', self.install_target)
534            print('please specify correct installation target ' +
535                  '(local | ecgate | cca)')
536            print('use -h or --help for help')
537            sys.exit(1)
538
539        if self.install_target and self.install_target != 'local':
540            if not self.ecgid or not self.ecuid or \
541               not self.gateway or not self.destination:
542                print('Please enter your ECMWF user id and group id as well ' +
543                      'as the \nname of the local gateway and the ectrans ' +
544                      'destination ')
545                print('with command line options --ecuid --ecgid \
546                       --gateway --destination')
547                print('Try "' + sys.argv[0].split('/')[-1] + \
548                      ' -h" to print usage information')
549                print('Please consult ecaccess documentation or ECMWF user \
550                       support for further details')
551                sys.exit(1)
552
553            if not self.flexpart_root_scripts:
554                self.flexpart_root_scripts = '${HOME}'
555            else:
556                self.flexpart_root_scripts = self.flexpart_root_scripts
557        else: # local
558            if not self.flexpart_root_scripts:
559                self.flexpart_root_scripts = _config.PATH_FLEXEXTRACT_DIR
560
561        return
562
563    def to_list(self):
564        '''
565        @Description:
566            Just generates a list of strings containing the attributes and
567            assigned values except the attributes "_expanded", "exedir",
568            "ecmwfdatadir" and "flexpart_root_scripts".
569
570        @Input:
571            self: instance of ControlFile class
572                Description see class documentation.
573
574        @Return:
575            l: list
576                A sorted list of the all ControlFile class attributes with
577                their values except the attributes "_expanded", "exedir",
578                "ecmwfdatadir" and "flexpart_root_scripts".
579        '''
580
581        import collections
582
583        attrs = collections.OrderedDict(sorted(vars(self).copy().items()))
584
585        l = list()
586
587        for item in attrs.items():
588            if '_expanded' in item[0]:
589                pass
590            elif 'exedir' in item[0]:
591                pass
592            elif 'flexpart_root_scripts' in item[0]:
593                pass
594            elif 'ecmwfdatadir' in item[0]:
595                pass
596            else:
597                if isinstance(item[1], list):
598                    stot = ''
599                    for s in item[1]:
600                        stot += s + ' '
601
602                    l.append("%s %s" % (item[0], stot))
603                else:
604                    l.append("%s %s" % item)
605
606        return sorted(l)
607
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG