source: flex_extract.git/source/python/classes/ControlFile.py @ 708c667

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

generated first sphinx instance and adapted code doctsrings for automated api generation for disaggregation module as a first test

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