source: flex_extract.git/source/python/classes/ControlFile.py @ ca867de

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

refactored functions in EcFlexpart? and did some minor changes

  • Property mode set to 100644
File size: 19.1 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.marsclass = None
116        self.stream = None
117        self.number = 'OFF'
118        self.expver = '1'
119        self.grid = None
120        self.area = ''
121        self.left = None
122        self.lower = None
123        self.upper = None
124        self.right = None
125        self.level = None
126        self.levelist = None
127        self.resol = None
128        self.gauss = 0
129        self.accuracy = 24
130        self.omega = 0
131        self.omegadiff = 0
132        self.eta = 0
133        self.etadiff = 0
134        self.etapar = 77
135        self.dpdeta = 1
136        self.smooth = 0
137        self.format = 'GRIB1'
138        self.addpar = None
139        self.prefix = 'EN'
140        self.cwc = 0
141        self.wrf = 0
142        self.ecfsdir = 'ectmp:/${USER}/econdemand/'
143        self.mailfail = ['${USER}']
144        self.mailops = ['${USER}']
145        self.grib2flexpart = 0
146        self.ecstorage = 0
147        self.ectrans = 0
148        self.inputdir = _config.PATH_INPUT_DIR
149        self.outputdir = None
150        self.ecmwfdatadir = _config.PATH_FLEXEXTRACT_DIR
151        self.exedir = _config.PATH_FORTRAN_SRC
152        self.flexpart_root_scripts = None
153        self.makefile = 'Makefile.gfortran'
154        self.destination = None
155        self.gateway = None
156        self.ecuid = None
157        self.ecgid = None
158        self.install_target = None
159        self.debug = 0
160        self.request = 0
161
162        self.logicals = ['gauss', 'omega', 'omegadiff', 'eta', 'etadiff',
163                         'dpdeta', 'cwc', 'wrf', 'grib2flexpart', 'ecstorage',
164                         'ectrans', 'debug', 'request']
165
166        self.__read_controlfile__()
167
168        return
169
170    def __read_controlfile__(self):
171        '''
172        @Description:
173            Read CONTROL file and assign all CONTROL file variables.
174
175        @Input:
176            self: instance of ControlFile class
177                Description see class documentation.
178
179        @Return:
180            <nothing>
181        '''
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        # basetime has only two possible values
358        if self.basetime:
359            if int(self.basetime) != 0 and int(self.basetime) != 12:
360                print('Basetime has an invalid value!')
361                print('Basetime = ' + str(self.basetime))
362                sys.exit(1)
363
364        # assure consistency of levelist and level
365        if self.levelist is None and self.level is None:
366            print('Warning: neither levelist nor level \
367                               specified in CONTROL file')
368            sys.exit(1)
369        elif self.levelist is None and self.level:
370            self.levelist = '1/to/' + self.level
371        elif (self.levelist and self.level is None) or \
372             (self.levelist[-1] != self.level[-1]):
373            self.level = self.levelist.split('/')[-1]
374        else:
375            pass
376
377        # if area was provided (only from commandline)
378        # decompose area into its 4 components
379        if self.area:
380            components = self.area.split('/')
381            # convert float to integer coordinates
382            if '.' in self.area:
383                components = [str(int(float(item) * 1000))
384                              for i, item in enumerate(components)]
385            self.upper, self.left, self.lower, self.right = components
386
387        # prepare step list if "/" signs are found
388        if '/' in self.step:
389            steps = self.step.split('/')
390            if 'to' in self.step.lower() and 'by' in self.step.lower():
391                ilist = np.arange(int(steps[0]),
392                                  int(steps[2]) + 1,
393                                  int(steps[4]))
394                self.step = ['{:0>3}'.format(i) for i in ilist]
395            elif 'to' in self.step.lower() and 'by' not in self.step.lower():
396                my_error(self.mailfail, self.step + ':\n' +
397                         'if "to" is used in steps parameter, \
398                         please use "by" as well')
399            else:
400                self.step = steps
401
402        # if maxstep wasn't provided
403        # search for it in the "step" parameter
404        if self.maxstep is None:
405            self.maxstep = 0
406            for s in self.step:
407                if int(s) > self.maxstep:
408                    self.maxstep = int(s)
409        else:
410            self.maxstep = int(self.maxstep)
411
412        # set root scripts since it is needed later on
413        if not self.flexpart_root_scripts:
414            self.flexpart_root_scripts = self.ecmwfdatadir
415
416        if not self.outputdir:
417            self.outputdir = self.inputdir
418
419        if not isinstance(self.mailfail, list):
420            if ',' in self.mailfail:
421                self.mailfail = self.mailfail.split(',')
422            elif ' ' in self.mailfail:
423                self.mailfail = self.mailfail.split()
424            else:
425                self.mailfail = [self.mailfail]
426
427        if not isinstance(self.mailops, list):
428            if ',' in self.mailops:
429                self.mailops = self.mailops.split(',')
430            elif ' ' in self.mailops:
431                self.mailops = self.mailops.split()
432            else:
433                self.mailops = [self.mailops]
434
435        if queue in ['ecgate', 'cca'] and \
436           not self.gateway or not self.destination or \
437           not self.ecuid or not self.ecgid:
438            print('\nEnvironment variables GATEWAY, DESTINATION, ECUID and \
439                   ECGID were not set properly!')
440            print('Please check for existence of file "ECMWF_ENV" in the \
441                   python directory!')
442            sys.exit(1)
443
444        if self.request != 0:
445            marsfile = os.path.join(self.inputdir,
446                                    _config.FILE_MARS_REQUESTS)
447            if os.path.isfile(marsfile):
448                os.remove(marsfile)
449
450        # check all logical variables for data type
451        # if its a string change to integer
452        for var in self.logicals:
453            if not isinstance(getattr(self, var), int):
454                setattr(self, var, int(getattr(self, var)))
455
456        return
457
458    def check_install_conditions(self):
459        '''
460        @Description:
461            Checks a couple of necessary attributes and conditions
462            for the installation such as if they exist and contain values.
463            Otherwise set default values.
464
465        @Input:
466            self: instance of ControlFile class
467                Description see class documentation.
468
469        @Return:
470            <nothing>
471        '''
472
473        if self.install_target and \
474           self.install_target not in ['local', 'ecgate', 'cca']:
475            print('ERROR: unknown or missing installation target ')
476            print('target: ', self.install_target)
477            print('please specify correct installation target ' +
478                  '(local | ecgate | cca)')
479            print('use -h or --help for help')
480            sys.exit(1)
481
482        if self.install_target and self.install_target != 'local':
483            if not self.ecgid or not self.ecuid or \
484               not self.gateway or not self.destination:
485                print('Please enter your ECMWF user id and group id as well ' +
486                      'as the \nname of the local gateway and the ectrans ' +
487                      'destination ')
488                print('with command line options --ecuid --ecgid \
489                       --gateway --destination')
490                print('Try "' + sys.argv[0].split('/')[-1] + \
491                      ' -h" to print usage information')
492                print('Please consult ecaccess documentation or ECMWF user \
493                       support for further details')
494                sys.exit(1)
495
496            if not self.flexpart_root_scripts:
497                self.flexpart_root_scripts = '${HOME}'
498            else:
499                self.flexpart_root_scripts = self.flexpart_root_scripts
500        else: # local
501            if not self.flexpart_root_scripts:
502                self.flexpart_root_scripts = _config.PATH_FLEXEXTRACT_DIR
503
504        return
505
506    def to_list(self):
507        '''
508        @Description:
509            Just generates a list of strings containing the attributes and
510            assigned values except the attributes "_expanded", "exedir",
511            "ecmwfdatadir" and "flexpart_root_scripts".
512
513        @Input:
514            self: instance of ControlFile class
515                Description see class documentation.
516
517        @Return:
518            l: list
519                A sorted list of the all ControlFile class attributes with
520                their values except the attributes "_expanded", "exedir",
521                "ecmwfdatadir" and "flexpart_root_scripts".
522        '''
523
524        import collections
525
526        attrs = collections.OrderedDict(sorted(vars(self).items()))
527
528        l = list()
529
530        for item in attrs.items():
531            if '_expanded' in item[0]:
532                pass
533            elif 'exedir' in item[0]:
534                pass
535            elif 'flexpart_root_scripts' in item[0]:
536                pass
537            elif 'ecmwfdatadir' in item[0]:
538                pass
539            else:
540                if isinstance(item[1], list):
541                    stot = ''
542                    for s in item[1]:
543                        stot += s + ' '
544
545                    l.append("%s %s" % (item[0], stot))
546                else:
547                    l.append("%s %s" % item)
548
549        return sorted(l)
550
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG