source: flex_extract.git/source/python/classes/ControlFile.py @ 25b14be

dev
Last change on this file since 25b14be was 25b14be, checked in by Anne Philipp <anne.philipp@…>, 16 months ago

changed whole tree structure of flex_extract to have better overview

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