source: flex_extract.git/python/ControlFile.py @ 3232589

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

restructuring, documentations and bug fixes

  • Property mode set to 100644
File size: 17.2 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 sys
56import inspect
57
58# ------------------------------------------------------------------------------
59# CLASS
60# ------------------------------------------------------------------------------
61class ControlFile(object):
62    '''
63    Class containing the information of the flex_extract CONTROL file.
64
65    Contains all the parameters of CONTROL file, which are e.g.:
66    DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME,
67    STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT,
68    LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY,
69    OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, SMOOTH, FORMAT,
70    ADDPAR, WRF, CWC, PREFIX, ECSTORAGE, ECTRANS, ECFSDIR,
71    MAILOPS, MAILFAIL, GRIB2FLEXPART, FLEXPARTDIR,
72    BASETIME, DATE_CHUNK, DEBUG, INPUTDIR, OUTPUTDIR, FLEXPART_ROOT_SCRIPTS
73
74    For more information about format and content of the parameter
75    see documentation.
76
77    '''
78
79    def __init__(self, filename):
80        '''
81        @Description:
82            Initialises the instance of ControlFile class and defines
83            all class attributes with default values. Afterwards calls
84            function __read_controlfile__ to read parameter from
85            Control file.
86
87        @Input:
88            self: instance of ControlFile class
89                Description see class documentation.
90
91            filename: string
92                Name of CONTROL file.
93
94        @Return:
95            <nothing>
96        '''
97
98        # list of all possible class attributes and their default values
99        self.controlfile = filename
100        self.start_date = None
101        self.end_date = None
102        self.date_chunk = 3
103        self.dtime = None
104        self.basetime = None
105        self.maxstep = None
106        self.type = None
107        self.time = None
108        self.step = None
109        self.marsclass = None
110        self.stream = None
111        self.number = 'OFF'
112        self.expver = None
113        self.grid = None
114        self.area = ''
115        self.left = None
116        self.lower = None
117        self.upper = None
118        self.right = None
119        self.level = None
120        self.levelist = None
121        self.resol = None
122        self.gauss = 0
123        self.accuracy = 24
124        self.omega = 0
125        self.omegadiff = 0
126        self.eta = 0
127        self.etadiff = 0
128        self.etapar = 77
129        self.dpdeta = 1
130        self.smooth = 0
131        self.format = 'GRIB1'
132        self.addpar = None
133        self.prefix = 'EN'
134        self.cwc = 0
135        self.wrf = 0
136        self.ecfsdir = 'ectmp:/${USER}/econdemand/'
137        self.mailfail = ['${USER}']
138        self.mailops = ['${USER}']
139        self.grib2flexpart = 0
140        self.ecstorage = 0
141        self.ectrans = 0
142        self.inputdir = '../work'
143        self.outputdir = self.inputdir
144        self.ecmwfdatadir = None
145        self.exedir = None
146        self.flexpart_root_scripts = None
147        self.makefile = None
148        self.destination = None
149        self.gateway = None
150        self.ecuid = None
151        self.ecgid = None
152        self.install_target = None
153        self.debug = 0
154
155        self.__read_controlfile__()
156
157        return
158
159    def __read_controlfile__(self):
160        '''
161        @Description:
162            Read CONTROL file and assign all CONTROL file variables.
163
164        @Input:
165            self: instance of ControlFile class
166                Description see class documentation.
167
168        @Return:
169            <nothing>
170        '''
171        from tools import my_error
172
173        # read whole CONTROL file
174        with open(self.controlfile) as f:
175            fdata = f.read().split('\n')
176
177        # go through every line and store parameter
178        for ldata in fdata:
179            data = ldata.split()
180            if len(data) > 1:
181                if 'm_' in data[0].lower():
182                    data[0] = data[0][2:]
183                if data[0].lower() == 'class':
184                    data[0] = 'marsclass'
185                if data[0].lower() == 'day1':
186                    data[0] = 'start_date'
187                if data[0].lower() == 'day2':
188                    data[0] = 'end_date'
189                if data[0].lower() == 'addpar':
190                    if '/' in data[1]:
191                        # remove leading '/' sign from addpar content
192                        if data[1][0] == '/':
193                            data[1] = data[1][1:]
194                        dd = data[1].split('/')
195                        data = [data[0]]
196                        for d in dd:
197                            data.append(d)
198                if len(data) == 2:
199                    if '$' in data[1]:
200                        setattr(self, data[0].lower(), data[1])
201                        while '$' in data[1]:
202                            i = data[1].index('$')
203                            j = data[1].find('{')
204                            k = data[1].find('}')
205                            var = os.getenv(data[1][j+1:k])
206                            if var is not None:
207                                data[1] = data[1][:i] + var + data[1][k+1:]
208                            else:
209                                my_error(self.mailfail,
210                                         'Could not find variable '
211                                         + data[1][j+1:k] + ' while reading ' +
212                                         self.controlfile)
213                        setattr(self, data[0].lower() + '_expanded', data[1])
214                    else:
215                        if data[1].lower() != 'none':
216                            setattr(self, data[0].lower(), data[1])
217                        else:
218                            setattr(self, data[0].lower(), None)
219                elif len(data) > 2:
220                    setattr(self, data[0].lower(), (data[1:]))
221            else:
222                pass
223
224        # script directory
225        self.ecmwfdatadir = os.path.dirname(os.path.abspath(inspect.getfile(
226            inspect.currentframe()))) + '/../'
227
228        # Fortran source directory
229        self.exedir = self.ecmwfdatadir + 'src/'
230
231        return
232
233    def __str__(self):
234        '''
235        @Description:
236            Prepares a string which have all the ControlFile
237            class attributes with its associated values.
238            Each attribute is printed in one line and in
239            alphabetical order.
240
241            Example:
242            'age': 10
243            'color': 'Spotted'
244            'kids': 0
245            'legs': 2
246            'name': 'Dog'
247            'smell': 'Alot'
248
249        @Input:
250            self: instance of ControlFile class
251                Description see class documentation.
252
253        @Return:
254            string of ControlFile class attributes with their values
255        '''
256        import collections
257
258        attrs = vars(self)
259        attrs = collections.OrderedDict(sorted(attrs.items()))
260
261        return '\n'.join("%s: %s" % item for item in attrs.items())
262
263    def assign_args_to_control(self, args):
264        '''
265        @Description:
266            Overwrites the existing ControlFile instance attributes with
267            the command line arguments.
268
269        @Input:
270            self: instance of ControlFile class
271                Description see class documentation.
272
273            args: instance of ArgumentParser
274                Contains the commandline arguments from script/program call.
275
276        @Return:
277            <nothing>
278        '''
279
280        # get dictionary of command line parameters and eliminate all
281        # parameters which are None (were not specified)
282        args_dict = vars(args)
283        arguments = {k : args_dict[k] for k in args_dict
284                     if args_dict[k] != None}
285
286        # assign all passed command line arguments to ControlFile instance
287        for k, v in arguments.iteritems():
288            setattr(self, str(k), v)
289
290        return
291
292    def assign_envs_to_control(self, envs):
293        '''
294        @Description:
295            Assigns the ECMWF environment parameter.
296
297        @Input:
298            envs: dict of strings
299                Contains the ECMWF environment parameternames "ECUID", "ECGID",
300                "DESTINATION" and "GATEWAY" with its corresponding values.
301                They were read from the file "ECMWF_ENV".
302
303        @Return:
304            <nothing>
305        '''
306
307        for k, v in envs.iteritems():
308            setattr(self, str(k).lower(), str(v))
309
310        return
311
312    def check_conditions(self):
313        '''
314        @Description:
315            Checks a couple of necessary attributes and conditions,
316            such as if they exist and contain values.
317            Otherwise set default values.
318
319        @Input:
320            self: instance of ControlFile class
321                Description see class documentation.
322
323        @Return:
324            <nothing>
325        '''
326        from tools import my_error
327        import numpy as np
328
329        # check for having at least a starting date
330        # otherwise program is not allowed to run
331        if self.start_date is None:
332            print 'start_date specified neither in command line nor ' + \
333                  'in CONTROL file ' +  self.controlfile
334            print 'Try "' + sys.argv[0].split('/')[-1] + \
335                  ' -h" to print usage information'
336            sys.exit(1)
337
338        # retrieve just one day if end_date isn't set
339        if self.end_date is None:
340            self.end_date = self.start_date
341
342        # assure consistency of levelist and level
343        if self.levelist is None:
344            if self.level is None:
345                print 'Warning: neither levelist nor level ' + \
346                      'specified in CONTROL file'
347                sys.exit(1)
348            else:
349                self.levelist = '1/to/' + self.level
350        else:
351            if 'to' in self.levelist.lower():
352                self.level = self.levelist.split('/')[2]
353            else:
354                self.level = self.levelist.split('/')[-1]
355
356        # if area was provided at command line
357        # decompse area into its 4 components
358        if self.area:
359            afloat = '.' in self.area
360            l = self.area.split('/')
361            if afloat:
362                for i, item in enumerate(l):
363                    item = str(int(float(item) * 1000))
364            self.upper, self.left, self.lower, self.right = l
365
366        # prepare step for correct usage
367        if '/' in self.step:
368            l = self.step.split('/')
369            if 'to' in self.step.lower():
370                if 'by' in self.step.lower():
371                    ilist = np.arange(int(l[0]), int(l[2]) + 1, int(l[4]))
372                    self.step = ['{:0>3}'.format(i) for i in ilist]
373                else:
374                    my_error(self.mailfail, self.step + ':\n' +
375                             'if "to" is used, please use "by" as well')
376            else:
377                self.step = l
378
379        # if maxstep wasn't provided
380        # search for it in the "step" parameter
381        if self.maxstep is None:
382            self.maxstep = 0
383            for s in self.step:
384                if int(s) > self.maxstep:
385                    self.maxstep = int(s)
386        else:
387            self.maxstep = int(self.maxstep)
388
389        # set root scripts since it is needed later on
390        if not self.flexpart_root_scripts:
391            self.flexpart_root_scripts = self.ecmwfdatadir
392
393        if not isinstance(self.mailfail, list):
394            if ',' in self.mailfail:
395                self.mailfail = self.mailfail.split(',')
396            elif ' ' in self.mailfail:
397                self.mailfail = self.mailfail.split()
398            else:
399                self.mailfail = [self.mailfail]
400
401        if not isinstance(self.mailops, list):
402            if ',' in self.mailops:
403                self.mailops = self.mailops.split(',')
404            elif ' ' in self.mailops:
405                self.mailops = self.mailops.split()
406            else:
407                self.mailops = [self.mailops]
408
409        if not self.gateway or not self.destination or \
410           not self.ecuid or not self.ecgid:
411            print '\nEnvironment variables GATWAY, DESTINATION, ECUID and ' + \
412                  'ECGID were not set properly!'
413            print 'Please check for excistence of file "ECMWF_ENV" in the ' + \
414                  'python directory!'
415            sys.exit(1)
416
417        return
418
419    def check_install_conditions(self):
420        '''
421        @Description:
422            Checks a couple of necessary attributes and conditions
423            for the installation such as if they exist and contain values.
424            Otherwise set default values.
425
426        @Input:
427            self: instance of ControlFile class
428                Description see class documentation.
429
430        @Return:
431            <nothing>
432        '''
433
434        if self.install_target and \
435           self.install_target not in ['local', 'ecgate', 'cca']:
436            print 'ERROR: unknown or missing installation target '
437            print 'target: ', self.install_target
438            print 'please specify correct installation target \
439                   (local | ecgate | cca)'
440            print 'use -h or --help for help'
441            sys.exit(1)
442
443        if self.install_target and self.install_target != 'local':
444            if not self.ecgid or not self.ecuid or \
445               not self.gateway or not self.destination:
446                print 'Please enter your ECMWF user id and group id as well as \
447                       the \nname of the local gateway and the ectrans \
448                       destination '
449                print 'with command line options --ecuid --ecgid \
450                       --gateway --destination'
451                print 'Try "' + sys.argv[0].split('/')[-1] + \
452                      ' -h" to print usage information'
453                print 'Please consult ecaccess documentation or ECMWF user \
454                       support for further details'
455                sys.exit(1)
456
457            if not self.flexpart_root_scripts:
458                self.flexpart_root_scripts = '${HOME}'
459            else:
460                self.flexpart_root_scripts = self.flexpart_root_scripts
461        else:
462            if not self.flexpart_root_scripts:
463                self.flexpart_root_scripts = '../'
464
465        if not self.makefile:
466            self.makefile = 'Makefile.gfortran'
467
468        return
469
470    def to_list(self):
471        '''
472        @Description:
473            Just generates a list of strings containing the attributes and
474            assigned values except the attributes "_expanded", "exedir",
475            "ecmwfdatadir" and "flexpart_root_scripts".
476
477        @Input:
478            self: instance of ControlFile class
479                Description see class documentation.
480
481        @Return:
482            l: list
483                A sorted list of the all ControlFile class attributes with
484                their values except the attributes "_expanded", "exedir",
485                "ecmwfdatadir" and "flexpart_root_scripts".
486        '''
487
488        import collections
489
490        attrs = collections.OrderedDict(sorted(vars(self).items()))
491
492        l = list()
493
494        for item in attrs.items():
495            if '_expanded' in item[0]:
496                pass
497            elif 'exedir' in item[0]:
498                pass
499            elif 'flexpart_root_scripts' in item[0]:
500                pass
501            elif 'ecmwfdatadir' in item[0]:
502                pass
503            else:
504                if isinstance(item[1], list):
505                    stot = ''
506                    for s in item[1]:
507                        stot += s + ' '
508
509                    l.append("%s %s" % (item[0], stot))
510                else:
511                    l.append("%s %s" % item)
512
513        return sorted(l)
514
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG