source: flex_extract.git/Testing/Regression/Mars_request/test_cmp_mars_requests.py @ d9abaac

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

after tarball check, applied: dos2unix conversion, some spell corrections, TAB to Space correction

  • Property mode set to 100755
File size: 10.3 KB
Line 
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""Comparison of the created MARS requests of two flex_extract versions.
4
5There will be comparisons for the given standard control files in the
6"Controls" - directory. The result of the comparison is stored in the
7"Log" - directory with an individual timestamp in the format %Y-%m-%d_%H-%M-%S.
8(E.g. log_2018-11-23_12-42-29)
9The MARS request files are named such that it contains the name of the
10corresponding control files "<control-identifier>.csv" (e.g. EA5_mr.csv).
11They are stored in the corresponding version directory and have the same
12name in both versions.
13
14The script should be called like:
15
16    python test_cmp_mars_requests.py <old_version_number> <new_version_number>
17
18Note
19----
20The MARS request files from the older version have to be in place already.
21The request files of the new/current version will be generated automatically
22with the "run_local.sh" script.
23It is necessary to have a directory named after the version number of
24flex_extract. For example: "7.0.3" and "7.1".
25
26Licence:
27--------
28    (C) Copyright 2014-2019.
29
30    SPDX-License-Identifier: CC-BY-4.0
31
32    This work is licensed under the Creative Commons Attribution 4.0
33    International License. To view a copy of this license, visit
34    http://creativecommons.org/licenses/by/4.0/ or send a letter to
35    Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
36
37Example
38-------
39    python test_cmp_mars_requests.py 7.0.4 7.1
40"""
41
42# ------------------------------------------------------------------------------
43# MODULES
44# ------------------------------------------------------------------------------
45import os
46import sys
47import pandas as pd
48import subprocess
49import shutil
50from datetime import datetime
51
52sys.path.append('../../../Source/Python')
53import _config
54from  Mods.tools import init128
55
56# ------------------------------------------------------------------------------
57# FUNCTION
58# ------------------------------------------------------------------------------
59def test_mr_column_equality(mr_old, mr_new):
60    '''Check if old and new version of MARS requests have the same
61    amount of columns.
62
63    If the number is not equal and/or the column names are not equal
64    an error message is stored in global variable "err_msg".
65
66    Parameters
67    ----------
68    mr_old : :obj:`pandas DataFrame`
69        Contains the mars requests from the old version of
70        flex_extract.
71
72    mr_new : :obj:`pandas DataFrame`
73        Contains the mars requests from the new version of
74        flex_extract.
75
76    Return
77    ------
78    bool
79        True if successful, False otherwise.
80    '''
81    global err_msg
82    if (len(mr_old.columns.values) == len(mr_new.columns.values) and
83        sorted(mr_old.columns.values) == sorted(mr_new.columns.values)):
84        return True
85    else:
86        err_msg = 'Unequal number and/or column names!\n'
87        return False
88
89
90def test_mr_number_equality(mr_old, mr_new):
91    '''Check if old and new version have the same number of requests.
92
93    If the number is not equal an error message is stored in
94    global variable "err_msg".
95
96    Parameters
97    ----------
98    mr_old : :obj:`pandas DataFrame`
99        Contains the mars requests from the old version of
100        flex_extract.
101
102    mr_new : :obj:`pandas DataFrame`
103        Contains the mars requests from the new version of
104        flex_extract.
105
106    Return
107    ------
108    bool
109        True if successful, False otherwise.
110    '''
111    global err_msg
112    if len(mr_new.index) == len(mr_old.index):
113        return True
114    else:
115        err_msg = 'Unequal number of mars requests!\n'
116        return False
117
118def test_mr_content_equality(mr_old, mr_new):
119    '''Check if old and new version have the same request contents.
120
121    If the content in a column is not equal an error message is stored in
122    global variable "err_msg", recording the column.
123
124    Parameters
125    ----------
126    mr_old : :obj:`pandas DataFrame`
127        Contains the mars requests from the old version of
128        flex_extract.
129
130    mr_new : :obj:`pandas DataFrame`
131        Contains the mars requests from the new version of
132        flex_extract.
133
134    Return
135    ------
136    bool
137        True if successful, False otherwise.
138    '''
139    global err_msg
140    lresult = None
141    columns = list(mr_new.columns.values)
142    del columns[columns.index('target')]
143    mr_new = trim_all_columns(mr_new)
144    mr_old = trim_all_columns(mr_old)
145    for col in columns:
146        if mr_new[col].equals(mr_old[col]):
147            lresult = True
148        else:
149            err_msg += 'Inconsistency in column: ' + col + '\n'
150            print("THERE SEEMS TO BE AN ERROR:")
151            print("CONTENT OF NEW VERSION:")
152            print(mr_new[col])
153            print("CONTENT OF OLD VERSION:")
154            print(mr_old[col])
155            return False
156    return lresult
157
158
159def trim_all_columns(df):
160    """
161    Trim whitespace from ends of each value across all series in dataframe
162    """
163    trim_strings = lambda x: x.strip() if isinstance(x, str) else x
164    return df.applymap(trim_strings)
165
166def convert_param_numbers(mr_old):
167    """
168    Convert the numbers parameter into integers with 3 digits.
169    """
170
171    if str(mr_old).strip() != "OFF" and mr_old != None and '/' in str(mr_old) :
172        numbers = mr_old.split('/')
173        number = str(int(numbers[0])).zfill(3)+'/TO/'+str(int(numbers[2])).zfill(3)
174
175        return number
176
177    return mr_old
178
179def convert_param_step(mr_old):
180    """
181    For pure forecast with steps greater than 23 hours, the older versions
182    writes out a list of steps instead with the syntax 'to' and 'by'.
183    e.g. 000/003/006/009/012/015/018/021/024/027/030/033/036
184   
185    Convert this to 0/to/36/by/3
186    """
187
188    #if 'to' in str(mr_old) and 'by' in str(mr_old):
189    #    steps = mr_old.split('/')
190    #    step = []
191    #    for i in range(int(steps[0]),int(steps[2]),int(steps[4])):
192    #        step.append(str(int(i)).zfill(2))
193    #    return '/'.join(step)
194   
195    if not mr_old.isdigit() and 'to' not in mr_old.lower():
196        if int(mr_old.split('/')[-1]) > 23:
197   
198            steps = mr_old.split('/')
199            dtime = int(steps[1]) - int(steps[0])
200           
201            nsteps = str(int(steps[1]))+'/to/'+str(int(steps[-1]))+'/by/'+str(int(dtime))
202            return nsteps           
203   
204    return mr_old
205
206def to_param_id(pars, table):
207    '''Transform parameter names to parameter ids with ECMWF grib table 128.
208
209    Parameters
210    ----------
211    pars : str
212        Addpar argument from CONTROL file in the format of
213        parameter names instead of ids. The parameter short
214        names are sepearted with "/" and they are passed as
215        one single string.
216
217    table : dict
218        Contains the ECMWF grib table 128 information.
219        The key is the parameter number and the value is the
220        short name of the parameter.
221
222    Return
223    ------
224    ipar : list of int
225        List of addpar parameters from CONTROL file transformed to
226        parameter ids in the format of integer.
227    '''
228    if not pars:
229        return []
230    if not isinstance(pars, str):
231        pars=str(pars)
232
233    cpar = pars.upper().split('/')
234    spar = []
235    for par in cpar:
236        par = par.strip()
237        for k, v in table.items():
238            if par.isdigit():
239                par = str(int(par)).zfill(3)
240            if par == k or par == v:
241                spar.append(k + '.128')
242                break
243        else:
244            print('\n\Warning: par ' + par + ' not found in table 128\n\n”')
245
246    return '/'.join(str(l) for l in spar)
247
248
249
250if __name__ == '__main__':
251
252    # basic values for paths and versions
253    control_path = 'Controls'
254    log_path = 'Log'
255    old_dir = sys.argv[1] # e.g. '7.0.4'
256    new_dir = sys.argv[2] # e.g. '7.1'
257    mr_filename = 'mars_requests.csv'
258
259    # have to be set to "True" in the beginnning
260    # so it only fails if a test fails
261    lfinal = True
262
263    # prepare log file for this test run
264    currenttime = datetime.now()
265    time_str = currenttime.strftime('%Y-%m-%d_%H-%M-%S')
266    logfile = os.path.join(log_path, 'log_' + time_str)
267    with open(logfile, 'a') as f:
268        f.write('Compare mars requests between version ' + old_dir +
269                ' and version ' + new_dir + ' : \n')
270
271    # list all controlfiles
272    controls =  os.listdir(control_path)
273
274    # loop over controlfiles
275    for c in controls:
276        # empty error message for every controlfile
277        err_msg = ''
278
279        # start flex_extract with controlfiles to get mars_request files
280        shutil.copy(os.path.join(control_path,c), _config.PATH_CONTROLFILES)
281        subprocess.check_output(['run_local.sh', new_dir, c])
282        os.remove(os.path.join(_config.PATH_CONTROLFILES,c))
283
284        # cut-of "CONTROL_" string and mv mars_reqeust file
285        # to a name including control specific name
286        mr_name = c.split('CONTROL_')[1] + '.csv'
287        shutil.move(os.path.join(new_dir,mr_filename), os.path.join(new_dir,mr_name))
288
289        # read both mr files (old & new)
290        mr_old = pd.read_csv(os.path.join(old_dir, mr_name))
291        mr_new = pd.read_csv(os.path.join(new_dir, mr_name))
292
293        mr_old.columns = mr_old.columns.str.strip()
294        mr_new.columns = mr_new.columns.str.strip()
295
296        # convert names in old to ids
297        table128 = init128(_config.PATH_GRIBTABLE)
298        #print mr_old['param']
299
300        # some format corrections are necessary to compare older versions with 7.1
301        mr_old['param'] = mr_old['param'].apply(to_param_id, args=[table128])
302        mr_old['number'] = mr_old['number'].apply(convert_param_numbers) 
303        if '142' in mr_old.loc[0,'param']: # if flux request
304            mr_old.loc[0,'step'] = convert_param_step(mr_old.loc[0,'step'])
305
306        print('Results: ', c)
307
308        # do tests on mr files
309        lcoleq = test_mr_column_equality(mr_old, mr_new)
310        lnoeq = test_mr_number_equality(mr_old, mr_new)
311        lcoeq = test_mr_content_equality(mr_old, mr_new)
312
313        # check results for mr file
314        lfinal = lfinal and lcoleq and lnoeq and lcoeq
315
316        # write out result to logging file
317        with open(logfile, 'a') as f:
318            if lcoleq and lnoeq and lcoeq:
319                f.write('... ' + c + ' ... OK!' + '\n')
320            else:
321                f.write('... ' + c + ' ... FAILED!' + '\n')
322                if err_msg:
323                    f.write('...    ' + err_msg + '\n')
324
325        exit
326
327    # exit with success or error status
328    if lfinal:
329        sys.exit(0) # 'SUCCESS'
330    else:
331        sys.exit(1) # 'FAIL'
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG