source: flex_extract.git/source/python/classes/ControlFile.py @ 4d3b052

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

introduced a new parameter which defines pure forcast mode

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