source: flex_extract.git/source/python/mods/checks.py @ d4696e0

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

bugfix retrievement with basetime parameter

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