source: flex_extract.git/source/python/mods/checks.py @ 79729d5

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

switched from python2 to python3

  • Property mode set to 100644
File size: 25.1 KB
Line 
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#*******************************************************************************
4# @Author: Anne Philipp (University of Vienna)
5#
6# @Date: November 2018
7#
8# @Change History:
9#
10# @License:
11#    (C) Copyright 2014-2019.
12#    Anne Philipp, Leopold Haimberger
13#
14#    This work is licensed under the Creative Commons Attribution 4.0
15#    International License. To view a copy of this license, visit
16#    http://creativecommons.org/licenses/by/4.0/ or send a letter to
17#    Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
18#*******************************************************************************
19'''This module contains check methods for the CONTROL paramaters.
20'''
21
22# ------------------------------------------------------------------------------
23# MODULES
24# ------------------------------------------------------------------------------
25
26import os
27import sys
28import _config
29try:
30    import exceptions
31except ImportError:
32    import builtins as exceptions
33from datetime import datetime
34import numpy as np
35from .tools import my_error, silent_remove
36# ------------------------------------------------------------------------------
37# FUNCTIONS
38# ------------------------------------------------------------------------------
39
40def check_logicals_type(c, logicals):
41    '''Check that the logical variables have correct type integer.
42
43    Parameters
44    ----------
45    c : ControlFile
46        Contains all the parameters of CONTROL file and
47        command line.
48
49    logicals : list of (str or int)
50        Names of the switches that are used to control the flow of the
51        program.
52
53    Return
54    ------
55
56    '''
57
58    for var in logicals:
59        if not isinstance(getattr(c, var), int):
60            setattr(c, var, int(getattr(c, var)))
61
62    return
63
64def check_grid(grid):
65    '''Convert grid into correct Lat/Lon format. E.g. '0.5/0.5'
66
67    Checks on format of original grid. Wether it is in the order of 1000 or 1.
68    Convert to correct grid format and substitute into "Lat/Lon" format string.
69
70    Parameters
71    ----------
72    grid : str
73        Contains grid information
74
75    Return
76    ------
77    grid : str
78        Contains grid in format Lat/lon. E.g. 0.1/0.1
79    '''
80
81    if 'N' in grid:
82        return grid
83    if '/' in grid:
84        gridx, gridy = grid.split('/')
85        if gridx == gridy:
86            grid = gridx
87        else:
88            raise ValueError('GRID parameter contains two values '
89                             'which are unequal %s' (grid))
90    # determine grid format
91    if float(grid) / 100. >= 0.5:
92        # grid is defined in 1/1000 degrees; old format
93        grid = '{}/{}'.format(float(grid) / 1000.,
94                              float(grid) / 1000.)
95    elif float(grid) / 100. < 0.5:
96        # grid is defined in normal degree; new format
97        grid = '{}/{}'.format(float(grid), float(grid))
98
99    return grid
100
101def check_area(grid, area, upper, lower, left , right):
102    '''Defines the correct area string.
103
104    Checks on the format of the four area components. Wether it is of
105    the order of 1000 or 1. Also checks wether area was already set by command
106    line, then the four components are overwritten.
107    Convert to correct format of the order of magnitude "1" and sets the
108    area parameter (North/West/South/East).
109    E.g.: -5./20./10./10.
110
111    Parameters
112    ----------
113    grid : str
114        Contains grid information.
115
116    area : str
117        Contains area informtion.
118
119    upper : str
120        The northern most latitude.
121
122    lower : str
123        The souther most latitude.
124
125    left : str
126        The western most longitude.
127
128    right : str
129        The eastern most longiude.
130
131    Return
132    ------
133    grid : str
134        Contains grid in format Lat/lon. E.g. 0.1/0.1
135    '''
136    if 'N' in grid:  # Gaussian output grid
137        area = 'G'
138        return area
139
140    # if area was provided decompose area into its 4 components
141    if area:
142        components = area.split('/')
143        upper, left, lower, right = components
144
145    # determine area format
146    if ((abs(float(upper) / 10000.) >= 0.01 or float(upper) / 1000. == 0. ) and
147        (abs(float(lower) / 10000.) >= 0.01 or float(lower) / 1000. == 0. ) and
148        (abs(float(left) / 10000.) >= 0.01 or float(left) / 1000. == 0. ) and
149        (abs(float(right) / 10000.) >= 0.01 or float(right) / 1000. == 0.)):
150        # area is defined in 1/1000 degrees; old format
151        area = '{}/{}/{}/{}'.format(float(upper) / 1000.,
152                                    float(left) / 1000.,
153                                    float(lower) / 1000.,
154                                    float(right) / 1000.)
155    elif (abs(float(upper) / 10000.) < 0.05 and
156          abs(float(lower) / 10000.) < 0.05 and
157          abs(float(left) / 10000.) < 0.05 and
158          abs(float(right) / 10000.) < 0.05):
159        # area is already in new format
160        area = '{}/{}/{}/{}'.format(float(upper),
161                                    float(left),
162                                    float(lower),
163                                    float(right))
164    else:
165        raise ValueError('The area components have different '
166                         'formats (upper, lower, left, right): '
167                         '{}/{}/{}/{}'.format(str(upper), str(lower),
168                                              str(left) , str(right)))
169
170    return area
171
172def check_levels(levelist, level):
173    '''Defines correct level list and guarantees that the maximum level is
174    one of the available maximum levels.
175
176    Parameters
177    ----------
178    levelist : str
179        Specifies the level list.
180        Examples: model level: 1/to/137, pressure levels: 500/to/1000
181
182    level : str
183        Specifies the maximum level.
184
185    Return
186    ------
187    levelist : str
188        Specifies the required levels. It has to have a valid
189        correspondence to the selected levtype.
190        Examples: model level: 1/to/137, pressure levels: 500/to/1000
191
192    level : str
193        Specifies the maximum level. It has to be one of the
194        available maximum level number as contained in the variable
195        MAX_LEVEL_LIST in "_config". E.g. [16, 19, 31, 40, 50, 60, 62, 91, 137]
196
197    '''
198    # assure consistency of levelist and level
199    if not levelist and not level:
200        raise ValueError('ERROR: neither levelist nor level '
201                         'specified in CONTROL file')
202    elif not levelist and level:
203        levelist = '1/to/' + level
204    elif (levelist and not level) or \
205         (levelist[-1] != level[-1]):
206        level = levelist.split('/')[-1]
207    else:
208        pass
209
210    # check if max level is a valid level
211    if int(level) not in _config.MAX_LEVEL_LIST:
212        raise ValueError('ERROR: \n'
213                         'LEVEL must be the maximum level of a specified '
214                         'level list from ECMWF, e.g. {} \n'
215                         'Check parameter "LEVEL" or the max level of '
216                         '"LEVELIST"!'.format(str(_config.MAX_LEVEL_LIST)))
217
218    return levelist, level
219
220
221def check_ppid(c, ppid):
222    '''Sets the current PPID.
223
224    Parameters
225    ----------
226    c : ControlFile
227            Contains all the parameters of CONTROL file and
228            command line.
229
230    ppid : int or None
231        Contains the ppid number provided by the command line parameter
232        of is None otherwise.
233
234    Return
235    ------
236
237    '''
238
239    if not ppid:
240        c.ppid = str(os.getppid())
241    else:
242        c.ppid = ppid
243
244    return
245
246
247def check_purefc(ftype):
248    '''Check for a pure forecast mode.
249
250    Parameters
251    ----------
252    ftype : list of str
253        List of field types.
254
255    Return
256    ------
257    True or False:
258        True if pure forecasts are to be retrieved. False if there are
259        analysis fields in between.
260    '''
261
262    if 'AN' not in ftype and '4V' not in ftype:
263        # pure forecast
264        return 1
265
266    return 0
267
268
269def check_step(step, mailfail):
270    '''Checks on step format and convert into a list of steps.
271
272    If the steps were defined with "to" and "by" they are converted into
273    a list of steps. If the steps were set in a string, it is
274    converted into a list.
275
276    Parameters
277    ----------
278    step : list of str or str
279        Specifies the forecast time step from forecast base time.
280        Valid values are hours (HH) from forecast base time.
281
282    mailfail : list of str
283        Contains all email addresses which should be notified.
284        It might also contain just the ecmwf user name which will trigger
285        mailing to the associated email address for this user.
286
287    Return
288    ------
289    step : list of str
290        List of forecast steps in format e.g. [001, 002, ...]
291    '''
292
293    if '/' in step:
294        steps = step.split('/')
295        if 'to' in step.lower() and 'by' in step.lower():
296            ilist = np.arange(int(steps[0]),
297                              int(steps[2]) + 1,
298                              int(steps[4]))
299            step = ['{:0>3}'.format(i) for i in ilist]
300        elif 'to' in step.lower() and 'by' not in step.lower():
301            my_error(step + ':\n' +
302                     'if "to" is used in steps parameter, '
303                     'please use "by" as well')
304        else:
305            step = steps
306
307    if not isinstance(step, list):
308        step = [step]
309
310    return step
311
312def check_type(ftype, steps):
313    '''Check if type variable is of type list and if analysis field has
314    forecast step 0.
315
316    Parameters
317    ----------
318    ftype : list of str or str
319        List of field types.
320
321    steps : str
322        Specifies the forecast time step from forecast base time.
323        Valid values are hours (HH) from forecast base time.
324
325    Return
326    ------
327    ftype : list of str
328        List of field types.
329    '''
330    if not isinstance(ftype, list):
331        ftype = [ftype]
332
333    for i, val in enumerate(ftype):
334        if ftype[i] == 'AN' and int(steps[i]) != 0:
335            print('Analysis retrievals must have STEP = 0 (now set to 0)')
336            ftype[i] = 0
337
338    return ftype
339
340def check_time(ftime):
341    '''Check if time variable is of type list. Otherwise convert to list.
342
343    Parameters
344    ----------
345    ftime : list of str or str
346        The time in hours of the field.
347
348    Return
349    ------
350    ftime : list of str
351        The time in hours of the field.
352    '''
353    if not isinstance(ftime, list):
354        ftime = [ftime]
355
356    return ftime
357
358def check_len_type_time_step(ftype, ftime, steps, maxstep, purefc):
359    '''Check if
360
361    Parameters
362    ----------
363    ftype : list of str
364        List of field types.
365
366    ftime : list of str or str
367        The time in hours of the field.
368
369    steps : str
370        Specifies the forecast time step from forecast base time.
371        Valid values are hours (HH) from forecast base time.
372
373    maxstep : int
374        The maximum forecast time step in hours from the forecast base time.
375        This is the maximum step for non flux (accumulated) forecast data.
376
377    purefc : int
378        Switch for definition of pure forecast mode or not.
379
380    Return
381    ------
382    ftype : list of str
383        List of field types.
384
385    ftime : list of str
386        The time in hours of the field.
387
388    steps : str
389        Specifies the forecast time step from forecast base time.
390        Valid values are hours (HH) from forecast base time.
391    '''
392    if not (len(ftype) == len(ftime) == len(steps)):
393        raise ValueError('ERROR: The number of field types, times and steps '
394                         'are not the same! Please check the setting in the '
395                         'CONTROL file!')
396
397    # if pure forecast is selected and only one field type/time is set
398    # prepare a complete list of type/time/step combination upto maxstep
399    if len(ftype) == 1 and purefc:
400        nftype = []
401        nsteps = []
402        nftime = []
403        for i in range(0, maxstep + 1):
404            nftype.append(ftype[0])
405            nsteps.append('{:0>3}'.format(i))
406            nftime.append(ftime[0])
407        return nftype, nftime, nsteps
408
409    return ftype, ftime, steps
410
411def check_mail(mail):
412    '''Check the string of mail addresses, seperate them and convert to a list.
413
414    Parameters
415    ----------
416    mail : list of str or str
417        Contains email addresses for notifications.
418        It might also contain just the ecmwf user name which will trigger
419        mailing to the associated email address for this user.
420
421    Return
422    ------
423    mail : list of str
424        Contains email addresses for notifications.
425        It might also contain just the ecmwf user name which will trigger
426        mailing to the associated email address for this user.
427
428    '''
429    if not isinstance(mail, list):
430        if ',' in mail:
431            mail = mail.split(',')
432        elif ' ' in mail:
433            mail = mail.split()
434        else:
435            mail = [mail]
436
437    return mail
438
439def check_queue(queue, gateway, destination, ecuid, ecgid):
440    '''Check if the necessary ECMWF parameters are set if the queue is
441    one of the QUEUES_LIST (in _config).
442
443    Parameters
444    ----------
445    queue : str
446        Name of the queue if submitted to the ECMWF servers.
447        Used to check if ecuid, ecgid, gateway and destination
448        are set correctly and are not empty.
449
450    gateway : str
451        The address of the gateway server.
452
453    destination : str
454        The name of the destination of the gateway server for data
455        transfer through ectrans. E.g. name@genericSftp
456
457    ecuid : str
458        ECMWF user id.
459
460    ecgid : str
461        ECMWF group id.
462
463    Return
464    ------
465
466    '''
467    if queue in _config.QUEUES_LIST and \
468        not gateway or not destination or \
469        not ecuid or not ecgid:
470        raise ValueError('\nEnvironment variables GATEWAY, DESTINATION, ECUID '
471                         'and ECGID were not set properly! \n '
472                         'Please check for existence of file "ECMWF_ENV" '
473                         'in the run directory!')
474    return
475
476def check_pathes(idir, odir, fpdir, fedir):
477    '''Check if output and flexpart pathes are set.
478
479    Parameters
480    ----------
481    idir : str
482        Path to the temporary directory for MARS retrieval data.
483
484    odir : str
485        Path to the final output directory where the FLEXPART input files
486        will be stored.
487
488    fpdir : str
489        Path to FLEXPART root directory.
490
491    fedir : str
492        Path to flex_extract root directory.
493
494    Return
495    ------
496    odir : str
497        Path to the final output directory where the FLEXPART input files
498        will be stored.
499
500    fpdir : str
501        Path to FLEXPART root directory.
502
503    '''
504    if not fpdir:
505        fpdir = fedir
506
507    if not odir:
508        odir = idir
509
510    return odir, fpdir
511
512def check_dates(start, end):
513    '''Checks if there is at least a start date for a one day retrieval.
514
515    Checks if end date lies after start date and end date is set.
516
517    Parameters
518    ----------
519    start : str
520        The start date of the retrieval job.
521
522    end : str
523        The end date of the retrieval job.
524
525    Return
526    ------
527    start : str
528        The start date of the retrieval job.
529
530    end : str
531        The end date of the retrieval job.
532
533    '''
534    # check for having at least a starting date
535    # otherwise program is not allowed to run
536    if not start:
537        raise ValueError('start_date was neither specified in command line nor '
538                         'in CONTROL file.\n'
539                         'Try "{} -h" to print usage information'
540                         .format(sys.argv[0].split('/')[-1]) )
541
542    # retrieve just one day if end_date isn't set
543    if not end:
544        end = start
545
546    dstart = datetime.strptime(start, '%Y%m%d')
547    dend = datetime.strptime(end, '%Y%m%d')
548    if dstart > dend:
549        raise ValueError('ERROR: Start date is after end date! \n'
550                         'Please adapt the dates in CONTROL file or '
551                         'command line! (start={}; end={})'.format(start, end))
552
553    return start, end
554
555def check_maxstep(maxstep, steps):
556    '''Convert maxstep into integer if it is already given. Otherwise, select
557    maxstep by going through the steps list.
558
559    Parameters
560    ----------
561    maxstep : str
562        The maximum forecast time step in hours from the forecast base time.
563        This is the maximum step for non flux (accumulated) forecast data.
564
565    steps : str
566        Specifies the forecast time step from forecast base time.
567        Valid values are hours (HH) from forecast base time.
568
569    Return
570    ------
571    maxstep : int
572        The maximum forecast time step in hours from the forecast base time.
573        This is the maximum step for non flux (accumulated) forecast data.
574
575    '''
576    # if maxstep wasn't provided
577    # search for it in the "step" parameter
578    if not maxstep:
579        maxstep = 0
580        for s in steps:
581            if int(s) > maxstep:
582                maxstep = int(s)
583    else:
584        maxstep = int(maxstep)
585
586    return maxstep
587
588def check_basetime(basetime):
589    '''Check if basetime is set and contains one of the two
590    possible values (0, 12).
591
592    Parameters
593    ----------
594    basetime : int or str or None
595        The time for a half day retrieval. The 12 hours upfront are to be
596        retrieved.
597
598    Return
599    ------
600    basetime : int or None
601        The time for a half day retrieval. The 12 hours upfront are to be
602        retrieved.
603    '''
604    if basetime is not None:
605        basetime = int(basetime)
606        if basetime != 0 and basetime != 12:
607            raise ValueError('ERROR: Basetime has an invalid value '
608                             '-> {}'.format(str(basetime)))
609    return basetime
610
611def check_request(request, marsfile):
612    '''Check if there is an old mars request file and remove it.
613
614    Parameters
615    ----------
616    request : int
617        Selects the mode of retrieval.
618        0: Retrieves the data from ECMWF.
619        1: Prints the mars requests to an output file.
620        2: Retrieves the data and prints the mars request.
621
622    marsfile : str
623        Path to the mars request file.
624
625    Return
626    ------
627
628    '''
629    if request != 0:
630        if os.path.isfile(marsfile):
631            silent_remove(marsfile)
632    return
633
634def check_public(public, dataset):
635    '''Check wether the dataset parameter is set for a
636    public data set retrieval.
637
638    Parameters
639    ----------
640    public : int
641        Specifies if public data are to be retrieved or not.
642
643    dataset : str
644        Specific name which identifies the public dataset.
645
646    Return
647    ------
648
649    '''
650    if public and not dataset:
651        raise ValueError('ERROR: If public mars data wants to be retrieved, '
652                         'the "dataset"-parameter has to be set too!')
653    return
654
655def check_acctype(acctype, ftype):
656    '''Guarantees that the accumulation field type is set.
657
658    If not set, it is derivated as in the old method (TYPE[1]).
659
660    Parameters
661    ----------
662    acctype : str
663        The field type for the accumulated forecast fields.
664
665    ftype : list of str
666        List of field types.
667
668    Return
669    ------
670    acctype : str
671        The field type for the accumulated forecast fields.
672    '''
673    if not acctype:
674        print('... Control parameter ACCTYPE was not defined.')
675        try:
676            if len(ftype) == 1 and ftype[0] != 'AN':
677                print('... Use same field type as for the non-flux fields.')
678                acctype = ftype[0]
679            elif len(ftype) > 1 and ftype[1] != 'AN':
680                print('... Use old setting by using TYPE[1] for flux forecast!')
681                acctype = ftype[1]
682        except:
683            raise ValueError('ERROR: Accumulation field type could not be set!')
684    else:
685        if acctype.upper() == 'AN':
686            raise ValueError('ERROR: Accumulation forecast fields can not be '
687                             'of type "analysis"!')
688    return acctype
689
690
691def check_acctime(acctime, marsclass, purefc):
692    '''Guarantees that the accumulation forecast times were set.
693
694    If it is not set, it tries to set the value for some of the
695    most commonly used data sets. Otherwise it raises an error.
696
697    Parameters
698    ----------
699    acctime : str
700        The starting time from the accumulated forecasts.
701
702    marsclass : str
703        ECMWF data classification identifier.
704
705    purefc : int
706        Switch for definition of pure forecast mode or not.
707
708    Return
709    ------
710    acctime : str
711        The starting time from the accumulated forecasts.
712    '''
713
714    if not acctime:
715        print('... Control parameter ACCTIME was not defined.')
716        print('... Value will be set depending on field type:\n '
717              '\t\t EA=06/18\n\t\t EI/OD=00/12\n\t\t EP=18')
718        if marsclass.upper() == 'EA': # Era 5
719            acctime = '06/18'
720        elif marsclass.upper() == 'EI': # Era-Interim
721            acctime = '00/12'
722        elif marsclass.upper() == 'EP': # CERA
723            acctime = '18'
724        elif marsclass.upper() == 'OD' and not purefc: # On-demand
725            acctime = '00/12'
726        else:
727            raise ValueError('ERROR: Accumulation forecast time can not '
728                             'automatically be derived!')
729    return acctime
730
731def check_accmaxstep(accmaxstep, marsclass, purefc, maxstep):
732    '''Guarantees that the accumulation forecast step were set.
733
734    Parameters
735    ----------
736    accmaxstep : str
737        The maximum forecast step for the accumulated forecast fields.
738
739    marsclass : str
740        ECMWF data classification identifier.
741
742    purefc : int
743        Switch for definition of pure forecast mode or not.
744
745    maxstep : str
746        The maximum forecast time step in hours from the forecast base time.
747        This is the maximum step for non flux (accumulated) forecast data.
748
749    Return
750    ------
751    accmaxstep : str
752        The maximum forecast step for the accumulated forecast fields.
753    '''
754    if not accmaxstep:
755        print('... Control parameter ACCMAXSTEP was not defined.')
756        print('... Value will be set depending on field type/time: '
757              '\n\t\t EA/EI/OD=12\n\t\t EP=24')
758        if marsclass.upper() in ['EA', 'EI', 'OD'] and not purefc:
759            # Era 5, Era-Interim, On-demand operational
760            accmaxstep = '12'
761        elif marsclass.upper() == 'EP': # CERA
762            accmaxstep = '24'
763        elif purefc and accmaxstep != maxstep:
764            accmaxstep = maxstep
765            print('... For pure forecast mode, the accumulated forecast must '
766                  'have the same maxstep as the normal forecast fields!\n'
767                  '\t\t Accmaxstep was set to maxstep!')
768        else:
769            raise ValueError('ERROR: Accumulation forecast step can not '
770                             'automatically be derived!')
771    else:
772        if purefc and int(accmaxstep) != int(maxstep):
773            accmaxstep = maxstep
774            print('... For pure forecast mode, the accumulated forecast must '
775                          'have the same maxstep as the normal forecast fields!\n'
776                          '\t\t Accmaxstep was set to maxstep!')
777    return accmaxstep
778
779def check_addpar(addpar):
780    '''Check that addpar has correct format of additional parameters in
781    a single string, so that it can be easily appended to the hard coded
782    parameters that are retrieved in any case.
783
784    Parameters
785    ----------
786    addpar : str or list of str
787        List of additional parameters to be retrieved.
788
789    Return
790    ------
791    addpar : str
792        List of additional parameters to be retrieved.
793    '''
794
795    if addpar and isinstance(addpar, str):
796        if '/' in addpar:
797            parlist = addpar.split('/')
798            parlist = [p for p in parlist if p is not '']
799        else:
800            parlist = [addpar]
801
802        addpar = '/' + '/'.join(parlist)
803
804    return addpar
805
806
807def check_job_chunk(job_chunk):
808    '''Checks that if job chunk is set, the number is positive and non zero.
809
810    Parameters
811    ----------
812    job_chunk : int
813        The number of days for a single job script.
814
815    Return
816    ------
817    job_chunk : int
818        The number of days for a single job script.
819    '''
820    if not job_chunk:
821        return job_chunk
822
823    if job_chunk < 0:
824        raise ValueError('ERROR: The number of job chunk is negative!\n'
825                         'It has to be a positive number!')
826    elif job_chunk == 0:
827        job_chunk = None
828    else:
829        pass
830
831    return job_chunk
832
833
834def check_number(number, mailfail):
835    '''Check for correct string format of ensemble member numbers.
836
837    Parameters
838    ----------
839    number : str
840        List of ensemble member forecast runs.
841
842    mailfail : list of str
843        Contains all email addresses which should be notified.
844        It might also contain just the ecmwf user name which will trigger
845        mailing to the associated email address for this user.
846
847    Return
848    ------
849    number : str
850        String with list of ensemble member forecast runs. E.g. '01/02/03/04'
851    '''
852
853    if '/' in number:
854        numbers = number.split('/')
855        if 'to' in number.lower() and 'by' in number.lower():
856            number = '{:0>3}'.format(int(numbers[0])) + '/TO/' + \
857                     '{:0>3}'.format(int(numbers[2])) + '/BY/' + \
858                     '{:0>3}'.format(int(numbers[4]))
859        elif 'to' in number.lower() and 'by' not in number.lower():
860            number = '{:0>3}'.format(int(numbers[0])) + '/TO/' + \
861                     '{:0>3}'.format(int(numbers[2]))
862        else:
863            numbers = ['{:0>3}'.format(i) for i in numbers]
864            number = '{:0>3}/'.join(numbers)
865    elif number.isdigit():
866        number = '{:0>3}'.format(int(number))
867    else:
868        pass
869
870    return number
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG