source: flex_extract.git/source/python/classes/ControlFile.py @ 274f9ef

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

Converted docstrings to numpy style and build first structure for sphinxdocumentation (incl API)

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