Source code for ControlFile

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#*******************************************************************************
# @Author: Leopold Haimberger (University of Vienna)
#
# @Date: November 2015
#
# @Change History:
#
#   February 2018 - Anne Philipp (University of Vienna):
#        - applied PEP8 style guide
#        - added documentation
#        - applied some minor modifications in programming style/structure
#        - changed name of class Control to ControlFile for more
#          self-explanation naming
#        - outsource of class ControlFile
#        - initialisation of class attributes ( to avoid high number of
#          conditional statements and set default values )
#        - divided assignment of attributes and the check of conditions
#        - outsourced the commandline argument assignments to control attributes
#   June 2020 - Anne Philipp
#        - update default makefile to None
#
# @License:
#    (C) Copyright 2014-2020.
#    Anne Philipp, Leopold Haimberger
#
#    SPDX-License-Identifier: CC-BY-4.0
#
#    This work is licensed under the Creative Commons Attribution 4.0
#    International License. To view a copy of this license, visit
#    http://creativecommons.org/licenses/by/4.0/ or send a letter to
#    Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
#*******************************************************************************

# ------------------------------------------------------------------------------
# MODULES
# ------------------------------------------------------------------------------
from __future__ import print_function

import os
import sys

# software specific classes and modules from flex_extract
#pylint: disable=wrong-import-position
sys.path.append('../')
import _config
from Mods.tools import my_error
from Mods.checks import (check_grid, check_area, check_levels, check_purefc,
                         check_step, check_mail, check_queue, check_pathes,
                         check_dates, check_maxstep, check_type, check_request,
                         check_basetime, check_public, check_acctype,
                         check_acctime, check_accmaxstep, check_time,
                         check_logicals_type, check_len_type_time_step,
                         check_addpar, check_job_chunk, check_number)
#pylint: enable=wrong-import-position

# ------------------------------------------------------------------------------
# CLASS
# ------------------------------------------------------------------------------
[docs]class ControlFile(object): ''' Contains the information which are stored in the CONTROL files. The CONTROL file is the steering part of the FLEXPART extraction software. All necessary parameters needed to retrieve the data fields from the MARS archive for driving FLEXPART are set in a CONTROL file. Some specific parameters like the start and end dates can be overwritten by the command line parameters, but in generall all parameters needed for a complete set of fields for FLEXPART can be set in the CONTROL file. Attributes ---------- controlfile : str The name of the control file to be processed. Default value is the filename passed to the init function when initialised. start_date : str The first day of the retrieval period. Default value is None. end_date :str The last day of the retrieval period. Default value is None. date_chunk : int Length of period for a single mars retrieval. Default value is 3. dtime :str The time step in hours. Default value is None. basetime : int The time for a half day retrieval. The 12 hours upfront are to be retrieved. Default value is None. maxstep : int The maximum forecast step for non flux data. Default value is None. type : list of str List of field type per retrieving hour. Default value is None. time : list of str List of retrieving times in hours. Default valuer is None. step : list of str or str List of forecast time steps in hours for non flux data. Default value is None. acctype : str The field type for the accumulated forecast fields. Default value is None. acctime : str The starting time of the accumulated forecasts. Default value is None. accmaxstep : int The maximum forecast step for the accumulated forecast fields (flux data). Default value is None. marsclass : str Characterisation of dataset. Default value is None. dataset : str For public datasets there is the specific naming and parameter dataset which has to be used to characterize the type of data. Default value is None. stream : str Identifies the forecasting system used to generate the data. Default value is None. number : str Selects the member in ensemble forecast run. Default value is 'OFF'. expver : str The version number of the dataset. Default value is '1'. gaussian : str This parameter is deprecated and should no longer be used. Specifies the desired type of Gaussian grid for the output. Default value is an empty string ''. grid : str Specifies the output grid which can be either a Gaussian grid or a Latitude/Longitude grid. Default value is None. area : str Specifies the desired sub-area of data to be extracted. Default value is None. left : str The western most longitude of the area to be extracted. Default value is None. lower : str The southern most latitude of the area to be extracted. Default value is None. upper : str The northern most latitued of the area to be extracted. Default value is None. right : str The eastern most longitude of the area to be extracted. Default value is None. level : str Specifies the maximum level. Default value is None. levelist : str Specifies the required level list. Default value is None. resol : str Specifies the desired triangular truncation of retrieved data, before carrying out any other selected post-processing. Default value is None. gauss : int Switch to select gaussian fields (1) or regular lat/lon (0). Default value is 0. accuracy : int Specifies the number of bits per value to be used in the generated GRIB coded fields. Default value is 24. omega : int Switch to select omega retrieval (1) or not (0). Default value is 0. omegadiff : int Switch to decide to calculate Omega and Dps/Dt from continuity equation for diagnostic purposes (1) or not (0). Default value is 0. eta : int Switch to select direct retrieval of etadot from MARS (1) or wether it has to be calculated (0). Then Default value is 0. etadiff : int Switch to select calculation of etadot and Dps/Dt from continuity equation for diagnostic purposes (1) or not (0). Default value is 0. etapar : int GRIB parameter Id for etadot fields. Default value is 77. dpdeta : int Switch to select multiplication of etadot with dpdeta. Default value is 1. smooth : int Spectral truncation of ETADOT after calculation on Gaussian grid. Default value is 0. format : str The format of the GRIB data. Default value is 'GRIB1'. addpar : str List of additional surface level ECMWF parameter to be retrieved. Default value is None. prefix : str Prefix string for the final FLEXPART/FLEXTRA ready input files. Default value is 'EN'. cwc : int Switch to select wether the sum of cloud liquid water content and cloud ice water content should be retrieved. Default value is 0. wrf : int Switch to select further parameters for retrievment to support WRF simulations. Default value is 0. ecfsdir : str Path to the ECMWF storage 'ectmp:/${USER}/econdemand/' mailfail : list of str Email list for sending error log files from ECMWF servers. The email addresses should be seperated by a comma. Default value is ['${USER}']. mailops : list of str Email list for sending operational log files from ECMWF servers. The email addresses should be seperated by a comma. Default value is ['${USER}']. ecstorage : int Switch to select storage of FLEXPART ready output files in the ECFS file system. Default value is 0. ectrans : int Switch to select the transfer of FLEXPART ready output files to the gateway server. Default value is 0. inputdir : str Path to the temporary directory for the retrieval grib files and other processing files. Default value is _config.PATH_INPUT_DIR. outputdir : str Path to the final directory where the final FLEXPART ready input files are stored. Default value is None. flexextractdir : str Path to the flex_extract root directory. Default value is _config.PATH_FLEXEXTRACT_DIR. exedir : str Path to the FORTRAN executable file. Default value is _config.PATH_FORTRAN_SRC. installdir : str Path to a FLEXPART root directory. Default value is None. makefile : str Name of the makefile to be used for the Fortran program. Default value is None. destination : str The remote destination which is used to transfer files from ECMWF server to local gateway server. Default value is None. gateway : str The gateway server the user is using. Default value is None. ecuid : str The user id on ECMWF server. Default value is None. ecgid : str The group id on ECMWF server. Default value is None. install_target : str Defines the location where the installation is to be done. Default value is None. debug : int Switch to keep temporary files at the end of postprocessing (1) or to delete all temporary files except the final output files (0). Default value is 0. oper : int Switch to prepare the operational job script. Start date, end date and basetime will be prepared with environment variables. Default value is 0. request : int Switch to select between just retrieving the data (0), writing the mars parameter values to a csv file (1) or doing both (2). Default value is 0. public : int Switch to select kind of ECMWF Web Api access and the possible data sets. Public data sets (1) and Memberstate data sets (0). Default value is 0. ec_api : boolean Tells wether the ECMWF Web API was able to load or not. Default value is None. cds_api : boolean Tells wether the CDS API was able to load or not. Default value is None. purefc : int Switch to decide wether the job is a pure forecast retrieval or coupled with analysis data. Default value is 0. rrint : int Switch to select between old precipitation disaggregation method (0) or the new IA3 disaggegration method (1). Default value is 0. doubleelda : int Switch to select the calculation of extra ensemble members for the ELDA stream. It doubles the amount of retrieved ensemble members. logicals : list of str List of the names of logical switches which controls the flow of the program. Default list is ['gauss', 'omega', 'omegadiff', 'eta', 'etadiff', 'dpdeta', 'cwc', 'wrf', 'ecstorage', 'ectrans', 'debug', 'request', 'public', 'purefc', 'rrint', 'doubleelda'] ''' def __init__(self, filename): '''Initialises the instance of ControlFile class and defines all class attributes with default values. Afterwards calls function __read_controlfile__ to read parameter from Control file. Parameters ---------- filename : str Name of CONTROL file. Return ------ ''' # list of all possible class attributes and their default values self.controlfile = filename self.start_date = None self.end_date = None self.date_chunk = 3 self.job_chunk = None self.dtime = None self.basetime = None self.maxstep = None self.type = None self.time = None self.step = None self.acctype = None self.acctime = None self.accmaxstep = None self.marsclass = None self.dataset = None self.stream = None self.number = 'OFF' self.expver = '1' self.gaussian = '' self.grid = None self.area = '' self.left = None self.lower = None self.upper = None self.right = None self.level = None self.levelist = None self.resol = None self.gauss = 0 self.accuracy = 24 self.omega = 0 self.omegadiff = 0 self.eta = 0 self.etadiff = 0 self.etapar = 77 self.dpdeta = 1 self.smooth = 0 self.format = 'GRIB1' self.addpar = None self.prefix = 'EN' self.cwc = 0 self.wrf = 0 self.ecfsdir = 'ectmp:/${USER}/econdemand/' self.mailfail = ['${USER}'] self.mailops = ['${USER}'] self.ecstorage = 0 self.ectrans = 0 self.inputdir = _config.PATH_INPUT_DIR self.outputdir = None self.flexextractdir = _config.PATH_FLEXEXTRACT_DIR self.exedir = _config.PATH_FORTRAN_SRC self.installdir = None self.makefile = None self.destination = None self.gateway = None self.ecuid = None self.ecgid = None self.install_target = None self.debug = 0 self.oper = 0 self.request = 0 self.public = 0 self.ec_api = None self.cds_api = None self.purefc = 0 self.rrint = 0 self.doubleelda = 0 self.logicals = ['gauss', 'omega', 'omegadiff', 'eta', 'etadiff', 'dpdeta', 'cwc', 'wrf', 'ecstorage', 'ectrans', 'debug', 'oper', 'request', 'public', 'purefc', 'rrint', 'doubleelda'] self._read_controlfile() return def _read_controlfile(self): '''Read CONTROL file and assign all CONTROL file variables. Parameters ---------- Return ------ ''' try: cfile = os.path.join(_config.PATH_CONTROLFILES, self.controlfile) with open(cfile) as f: fdata = f.read().split('\n') except IOError: print('Could not read CONTROL file "' + cfile + '"') print('Either it does not exist or its syntax is wrong.') print('Try "' + sys.argv[0].split('/')[-1] + \ ' -h" to print usage information') sys.exit(1) # go through every line and store parameter for ldata in fdata: if ldata and ldata[0] == '#': # ignore comment line in control file continue if '#' in ldata: # cut off comment ldata = ldata.split('#')[0] data = ldata.split() if len(data) > 1: if 'm_' in data[0].lower(): data[0] = data[0][2:] if data[0].lower() == 'class': data[0] = 'marsclass' if data[0].lower() == 'day1': data[0] = 'start_date' if data[0].lower() == 'day2': data[0] = 'end_date' if len(data) == 2: if '$' in data[1]: setattr(self, data[0].lower(), data[1]) while '$' in data[1]: i = data[1].index('$') j = data[1].find('{') k = data[1].find('}') var = os.getenv(data[1][j+1:k]) if var is not None: data[1] = data[1][:i] + var + data[1][k+1:] else: my_error('Could not find variable ' + data[1][j+1:k] + ' while reading ' + self.controlfile) setattr(self, data[0].lower() + '_expanded', data[1]) else: if data[1].lower() != 'none': setattr(self, data[0].lower(), data[1]) else: setattr(self, data[0].lower(), None) elif len(data) > 2: setattr(self, data[0].lower(), (data[1:])) else: pass return def __str__(self): '''Prepares a string which have all the ControlFile class attributes with its associated values. Each attribute is printed in one line and in alphabetical order. Example ------- 'age': 10 'color': 'Spotted' 'kids': 0 'legs': 2 'name': 'Dog' 'smell': 'Alot' Parameters ---------- Return ------ string Single string of concatenated ControlFile class attributes with their values ''' import collections attrs = vars(self).copy() attrs = collections.OrderedDict(sorted(attrs.items())) return '\n'.join("%s: %s" % item for item in attrs.items())
[docs] def assign_args_to_control(self, args): '''Overwrites the existing ControlFile instance attributes with the command line arguments. Parameters ---------- args : Namespace Contains the commandline arguments from script/program call. Return ------ ''' # get dictionary of command line parameters and eliminate all # parameters which are None (were not specified) args_dict = vars(args) arguments = {k : args_dict[k] for k in args_dict if args_dict[k] != None} # assign all passed command line arguments to ControlFile instance for k, v in arguments.items(): setattr(self, str(k), v) return
[docs] def assign_envs_to_control(self, envs): '''Assigns the ECMWF environment parameter. Parameters ---------- envs : dict of str Contains the ECMWF environment parameternames "ECUID", "ECGID", "DESTINATION" and "GATEWAY" with its corresponding values. They were read from the file "ECMWF_ENV". Return ------ ''' for k, v in envs.items(): setattr(self, str(k).lower(), str(v)) return
[docs] def check_conditions(self, queue): '''Checks a couple of necessary attributes and conditions, such as if they exist and contain values. Otherwise set default values. Parameters ---------- queue : str Name of the queue if submitted to the ECMWF servers. Used to check if ecuid, ecgid, gateway and destination are set correctly and are not empty. Return ------ ''' check_logicals_type(self, self.logicals) self.mailfail = check_mail(self.mailfail) self.mailops = check_mail(self.mailops) check_queue(queue, self.gateway, self.destination, self.ecuid, self.ecgid) self.outputdir, self.installdir = check_pathes(self.inputdir, self.outputdir, self.installdir, self.flexextractdir) self.start_date, self.end_date = check_dates(self.start_date, self.end_date) self.basetime = check_basetime(self.basetime) self.levelist, self.level = check_levels(self.levelist, self.level) self.step = check_step(self.step) self.maxstep = check_maxstep(self.maxstep, self.step) check_request(self.request, os.path.join(self.inputdir, _config.FILE_MARS_REQUESTS)) check_public(self.public, self.dataset, self.marsclass) self.type = check_type(self.type, self.step) self.time = check_time(self.time) self.purefc = check_purefc(self.type) self.type, self.time, self.step = check_len_type_time_step(self.type, self.time, self.step, self.maxstep, self.purefc) self.acctype = check_acctype(self.acctype, self.type) self.acctime = check_acctime(self.acctime, self.marsclass, self.purefc, self.time) self.accmaxstep = check_accmaxstep(self.accmaxstep, self.marsclass, self.purefc, self.maxstep) self.grid = check_grid(self.grid) self.area = check_area(self.grid, self.area, self.upper, self.lower, self.left, self.right) self.addpar = check_addpar(self.addpar) self.job_chunk = check_job_chunk(self.job_chunk) self.number = check_number(self.number) return
[docs] def to_list(self): '''Just generates a list of strings containing the attributes and assigned values except the attributes "_expanded", "exedir", "flexextractdir" and "installdir". Parameters ---------- Return ------ l : list of * A sorted list of the all ControlFile class attributes with their values except the attributes "_expanded", "exedir", "flexextractdir" and "installdir". ''' import collections attrs = collections.OrderedDict(sorted(vars(self).copy().items())) l = list() for item in attrs.items(): if '_expanded' in item[0]: pass elif 'exedir' in item[0]: pass elif 'installdir' in item[0]: pass elif 'flexextractdir' in item[0]: pass else: if isinstance(item[1], list): stot = '' for s in item[1]: stot += s + ' ' l.append("%s %s\n" % (item[0], stot)) else: l.append("%s %s\n" % item) return sorted(l)