source: flex_extract.git/source/python/classes/ControlFile.py @ 4971f63

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

eliminated some redundancy and exchanged CONTROL2 with the config-filename

  • Property mode set to 100644
File size: 18.7 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
183        try:
184            with open(os.path.join(_config.PATH_CONTROLFILES,
185                                   self.controlfile)) as f:
186                fdata = f.read().split('\n')
187        except IOError:
188            print('Could not read CONTROL file "' + args.controlfile + '"')
189            print('Either it does not exist or its syntax is wrong.')
190            print('Try "' + sys.argv[0].split('/')[-1] + \
191                      ' -h" to print usage information')
192            sys.exit(1)
193
194        # go through every line and store parameter
195        for ldata in fdata:
196            data = ldata.split()
197            if len(data) > 1:
198                if 'm_' in data[0].lower():
199                    data[0] = data[0][2:]
200                if data[0].lower() == 'class':
201                    data[0] = 'marsclass'
202                if data[0].lower() == 'day1':
203                    data[0] = 'start_date'
204                if data[0].lower() == 'day2':
205                    data[0] = 'end_date'
206                if data[0].lower() == 'addpar':
207                    if '/' in data[1]:
208                        # remove leading '/' sign from addpar content
209                        if data[1][0] == '/':
210                            data[1] = data[1][1:]
211                        dd = data[1].split('/')
212                        data = [data[0]]
213                        for d in dd:
214                            data.append(d)
215                if len(data) == 2:
216                    if '$' in data[1]:
217                        setattr(self, data[0].lower(), data[1])
218                        while '$' in data[1]:
219                            i = data[1].index('$')
220                            j = data[1].find('{')
221                            k = data[1].find('}')
222                            var = os.getenv(data[1][j+1:k])
223                            if var is not None:
224                                data[1] = data[1][:i] + var + data[1][k+1:]
225                            else:
226                                my_error(self.mailfail,
227                                         'Could not find variable '
228                                         + data[1][j+1:k] + ' while reading ' +
229                                         self.controlfile)
230                        setattr(self, data[0].lower() + '_expanded', data[1])
231                    else:
232                        if data[1].lower() != 'none':
233                            setattr(self, data[0].lower(), data[1])
234                        else:
235                            setattr(self, data[0].lower(), None)
236                elif len(data) > 2:
237                    setattr(self, data[0].lower(), (data[1:]))
238            else:
239                pass
240
241        return
242
243    def __str__(self):
244        '''
245        @Description:
246            Prepares a string which have all the ControlFile
247            class attributes with its associated values.
248            Each attribute is printed in one line and in
249            alphabetical order.
250
251            Example:
252            'age': 10
253            'color': 'Spotted'
254            'kids': 0
255            'legs': 2
256            'name': 'Dog'
257            'smell': 'Alot'
258
259        @Input:
260            self: instance of ControlFile class
261                Description see class documentation.
262
263        @Return:
264            string of ControlFile class attributes with their values
265        '''
266        import collections
267
268        attrs = vars(self)
269        attrs = collections.OrderedDict(sorted(attrs.items()))
270
271        return '\n'.join("%s: %s" % item for item in attrs.items())
272
273    def assign_args_to_control(self, args):
274        '''
275        @Description:
276            Overwrites the existing ControlFile instance attributes with
277            the command line arguments.
278
279        @Input:
280            self: instance of ControlFile class
281                Description see class documentation.
282
283            args: instance of ArgumentParser
284                Contains the commandline arguments from script/program call.
285
286        @Return:
287            <nothing>
288        '''
289
290        # get dictionary of command line parameters and eliminate all
291        # parameters which are None (were not specified)
292        args_dict = vars(args)
293        arguments = {k : args_dict[k] for k in args_dict
294                     if args_dict[k] != None}
295
296        # assign all passed command line arguments to ControlFile instance
297        for k, v in arguments.iteritems():
298            setattr(self, str(k), v)
299
300        return
301
302    def assign_envs_to_control(self, envs):
303        '''
304        @Description:
305            Assigns the ECMWF environment parameter.
306
307        @Input:
308            envs: dict of strings
309                Contains the ECMWF environment parameternames "ECUID", "ECGID",
310                "DESTINATION" and "GATEWAY" with its corresponding values.
311                They were read from the file "ECMWF_ENV".
312
313        @Return:
314            <nothing>
315        '''
316
317        for k, v in envs.iteritems():
318            setattr(self, str(k).lower(), str(v))
319
320        return
321
322    def check_conditions(self, queue):
323        '''
324        @Description:
325            Checks a couple of necessary attributes and conditions,
326            such as if they exist and contain values.
327            Otherwise set default values.
328
329        @Input:
330            self: instance of ControlFile class
331                Description see class documentation.
332
333            queue: string
334                Name of the queue if submitted to the ECMWF servers.
335                Used to check if ecuid, ecgid, gateway and destination
336                are set correctly and are not empty.
337
338        @Return:
339            <nothing>
340        '''
341        from mods.tools import my_error
342        import numpy as np
343
344        # check for having at least a starting date
345        # otherwise program is not allowed to run
346        if self.start_date is None:
347            print('start_date specified neither in command line nor \
348                   in CONTROL file ' +  self.controlfile)
349            print('Try "' + sys.argv[0].split('/')[-1] +
350                  ' -h" to print usage information')
351            sys.exit(1)
352
353        # retrieve just one day if end_date isn't set
354        if self.end_date is None:
355            self.end_date = self.start_date
356
357        # assure consistency of levelist and level
358        if self.levelist is None and self.level is None:
359            print('Warning: neither levelist nor level \
360                               specified in CONTROL file')
361            sys.exit(1)
362        elif self.levelist is None and self.level:
363            self.levelist = '1/to/' + self.level
364        elif (self.levelist and self.level is None) or \
365             (self.levelist[-1] != self.level[-1]):
366            self.level = self.levelist.split('/')[-1]
367        else:
368            pass
369
370        # if area was provided (only from commandline)
371        # decompose area into its 4 components
372        if self.area:
373            components = self.area.split('/')
374            # convert float to integer coordinates
375            if '.' in self.area:
376                components = [str(int(float(item) * 1000))
377                              for i, item in enumerate(components)]
378            self.upper, self.left, self.lower, self.right = components
379
380        # prepare step list if "/" signs are found
381        if '/' in self.step:
382            steps = self.step.split('/')
383            if 'to' in self.step.lower() and 'by' in self.step.lower():
384                ilist = np.arange(int(steps[0]),
385                                  int(steps[2]) + 1,
386                                  int(steps[4]))
387                self.step = ['{:0>3}'.format(i) for i in ilist]
388            elif 'to' in self.step.lower() and 'by' not in self.step.lower():
389                my_error(self.mailfail, self.step + ':\n' +
390                         'if "to" is used in steps parameter, \
391                         please use "by" as well')
392            else:
393                self.step = steps
394
395        # if maxstep wasn't provided
396        # search for it in the "step" parameter
397        if self.maxstep is None:
398            self.maxstep = 0
399            for s in self.step:
400                if int(s) > self.maxstep:
401                    self.maxstep = int(s)
402        else:
403            self.maxstep = int(self.maxstep)
404
405        # set root scripts since it is needed later on
406        if not self.flexpart_root_scripts:
407            self.flexpart_root_scripts = self.ecmwfdatadir
408
409        if not isinstance(self.mailfail, list):
410            if ',' in self.mailfail:
411                self.mailfail = self.mailfail.split(',')
412            elif ' ' in self.mailfail:
413                self.mailfail = self.mailfail.split()
414            else:
415                self.mailfail = [self.mailfail]
416
417        if not isinstance(self.mailops, list):
418            if ',' in self.mailops:
419                self.mailops = self.mailops.split(',')
420            elif ' ' in self.mailops:
421                self.mailops = self.mailops.split()
422            else:
423                self.mailops = [self.mailops]
424
425        if queue in ['ecgate', 'cca'] and \
426           not self.gateway or not self.destination or \
427           not self.ecuid or not self.ecgid:
428            print('\nEnvironment variables GATEWAY, DESTINATION, ECUID and \
429                   ECGID were not set properly!')
430            print('Please check for existence of file "ECMWF_ENV" in the \
431                   python directory!')
432            sys.exit(1)
433
434        if self.request != 0:
435            marsfile = os.path.join(self.inputdir,
436                                    _config.FILE_MARS_REQUESTS)
437            if os.path.isfile(marsfile):
438                os.remove(marsfile)
439
440        # check all logical variables for data type
441        # if its a string change to integer
442        for var in self.logicals:
443            if not isinstance(getattr(self, var), int):
444                setattr(self, var, int(getattr(self, var)))
445
446        return
447
448    def check_install_conditions(self):
449        '''
450        @Description:
451            Checks a couple of necessary attributes and conditions
452            for the installation such as if they exist and contain values.
453            Otherwise set default values.
454
455        @Input:
456            self: instance of ControlFile class
457                Description see class documentation.
458
459        @Return:
460            <nothing>
461        '''
462
463        if self.install_target and \
464           self.install_target not in ['local', 'ecgate', 'cca']:
465            print('ERROR: unknown or missing installation target ')
466            print('target: ', self.install_target)
467            print('please specify correct installation target ' +
468                  '(local | ecgate | cca)')
469            print('use -h or --help for help')
470            sys.exit(1)
471
472        if self.install_target and self.install_target != 'local':
473            if not self.ecgid or not self.ecuid or \
474               not self.gateway or not self.destination:
475                print('Please enter your ECMWF user id and group id as well ' +
476                      'as the \nname of the local gateway and the ectrans ' +
477                      'destination ')
478                print('with command line options --ecuid --ecgid \
479                       --gateway --destination')
480                print('Try "' + sys.argv[0].split('/')[-1] + \
481                      ' -h" to print usage information')
482                print('Please consult ecaccess documentation or ECMWF user \
483                       support for further details')
484                sys.exit(1)
485
486            if not self.flexpart_root_scripts:
487                self.flexpart_root_scripts = '${HOME}'
488            else:
489                self.flexpart_root_scripts = self.flexpart_root_scripts
490        else: # local
491            if not self.flexpart_root_scripts:
492                self.flexpart_root_scripts = _config.PATH_FLEXEXTRACT_DIR
493
494        return
495
496    def to_list(self):
497        '''
498        @Description:
499            Just generates a list of strings containing the attributes and
500            assigned values except the attributes "_expanded", "exedir",
501            "ecmwfdatadir" and "flexpart_root_scripts".
502
503        @Input:
504            self: instance of ControlFile class
505                Description see class documentation.
506
507        @Return:
508            l: list
509                A sorted list of the all ControlFile class attributes with
510                their values except the attributes "_expanded", "exedir",
511                "ecmwfdatadir" and "flexpart_root_scripts".
512        '''
513
514        import collections
515
516        attrs = collections.OrderedDict(sorted(vars(self).items()))
517
518        l = list()
519
520        for item in attrs.items():
521            if '_expanded' in item[0]:
522                pass
523            elif 'exedir' in item[0]:
524                pass
525            elif 'flexpart_root_scripts' in item[0]:
526                pass
527            elif 'ecmwfdatadir' in item[0]:
528                pass
529            else:
530                if isinstance(item[1], list):
531                    stot = ''
532                    for s in item[1]:
533                        stot += s + ' '
534
535                    l.append("%s %s" % (item[0], stot))
536                else:
537                    l.append("%s %s" % item)
538
539        return sorted(l)
540
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG