source: flex_extract.git/source/python/classes/ControlFile.py @ 3f36e42

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

outsourced some checks from CONTROL class to checks module; translated namelist generation by genshi templating; corrected a bug in grib2 conversion

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