diff --git a/Src/cmor.c b/Src/cmor.c index f6798044..01c6f6b6 100644 --- a/Src/cmor.c +++ b/Src/cmor.c @@ -6719,9 +6719,11 @@ int calculate_leadtime_coord(int var_id) { /* activate define mode */ nc_redef(ncid); /* add leadtime */ - if (ierr = nc_def_var(ncid, "leadtime", NC_DOUBLE, 1, &time_dim, &leadtime)) { - snprintf(msg, CMOR_MAX_STRING, "cannot add 'leadtime' variable"); - cmor_handle_error(msg, CMOR_CRITICAL); + if (ierr = nc_inq_varid(ncid, "leadtime", &leadtime)) { + if (ierr = nc_def_var(ncid, "leadtime", NC_DOUBLE, 1, &time_dim, &leadtime)) { + snprintf(msg, CMOR_MAX_STRING, "cannot add 'leadtime' variable"); + cmor_handle_error(msg, CMOR_CRITICAL); + } } /* variable attributes */ diff --git a/Test/test_python_forecast_coordinates.py b/Test/test_python_forecast_coordinates.py index a4d0a77d..2f89e397 100644 --- a/Test/test_python_forecast_coordinates.py +++ b/Test/test_python_forecast_coordinates.py @@ -2,6 +2,7 @@ import numpy as np import cmor import unittest +import os from netCDF4 import Dataset @@ -31,7 +32,7 @@ "parent_activity_id": "no parent", "physics_index": "1", "realization_index": "1", - "source": "HadGEM3-GC31-LL", + "source": "HadGEM3-GC31-LL (2016): \naerosol: UKCA-GLOMAP-mode\natmos: MetUM-HadGEM3-GA7.1 (N96; 192 x 144 longitude/latitude; 85 levels; top level 85 km)\natmosChem: none\nland: JULES-HadGEM3-GL7.1\nlandIce: none\nocean: NEMO-HadGEM3-GO6.0 (eORCA1 tripolar primarily 1 deg with meridional refinement down to 1/3 degree in the tropics; 360 x 330 longitude/latitude; 75 levels; top grid cell 0-1 m)\nocnBgchem: none\nseaIce: CICE-HadGEM3-GSI8 (eORCA1 tripolar primarily 1 deg; 360 x 330 longitude/latitude)", "source_id": "HadGEM3-GC31-LL", "source_type": "AGCM", "sub_experiment": "none", @@ -59,6 +60,8 @@ def setUp(self): if error_flag: raise RuntimeError("CMOR dataset_json call failed") + + def test_has_forcast_coordinates(self): # load MIP table mip_table = "CMIP6_Amon_leadtime.json" table_id = cmor.load_table(mip_table) @@ -82,13 +85,101 @@ def setUp(self): reshaped_data = data.reshape((5, 5, 2, 1, 1)) # write data cmor.write(tas_var_id, reshaped_data) - self.filename = cmor.close(tas_var_id, file_name=True) + filename = cmor.close(tas_var_id, file_name=True) - def test_has_forcast_coordinates(self): - ds = Dataset(self.filename) + ds = Dataset(filename) np.testing.assert_array_equal(ds.variables['reftime'][:].data, [10.0]) np.testing.assert_array_equal(ds.variables['leadtime'][:].data, [20.0, 50.0]) ds.close() + os.remove(filename) + + +class TestWriteSlices(unittest.TestCase): + + def setUp(self): + """ + Write out a simple file using CMOR + """ + # Set up CMOR + cmor.setup(inpath="TestTables", netcdf_file_action=cmor.CMOR_REPLACE, + logfile="cmor.log", create_subdirectories=0) + + # Define dataset using DATASET_INFO + with open("Test/input_leadtime.json", "w") as input_file_handle: + json.dump(DATASET_INFO, input_file_handle, sort_keys=True, indent=4) + + # read dataset info + error_flag = cmor.dataset_json("Test/input_leadtime.json") + if error_flag: + raise RuntimeError("CMOR dataset_json call failed") + + + def test_multiple_writes(self): + # load MIP table + mip_table = "CMIP6_Amon.json" + table_id = cmor.load_table(mip_table) + + # construct axes + time = cmor.axis(table_entry="time", units="days since 2000-01-01") + height2m = cmor.axis(table_entry="height2m", units="m", coord_vals=np.array((2.0,))) + latitude = cmor.axis(table_entry="latitude", units="degrees_north", + coord_vals=np.array(range(5)), + cell_bounds=np.array(range(6))) + longitude = cmor.axis(table_entry="longitude", units="degrees_east", + coord_vals=np.array(range(5)), + cell_bounds=np.array(range(6))) + axis_ids = [longitude, latitude, time, height2m] + tas_var_id = cmor.variable(table_entry="tas", axis_ids=axis_ids, units="K") + # write data + for year in range(2): + data = np.random.random(25*12) + reshaped_data = data.reshape((5, 5, 12, 1)) + time_vals = [pt*30 + 360.0 * year + 15.0 for pt in range(12)] + time_bnds = [360.0 * year ] + [pt*30 + 360.0 * year + 30.0 for pt in range(12)] + cmor.write(tas_var_id, reshaped_data, time_vals=time_vals, time_bnds=time_bnds) + + filename = cmor.close(tas_var_id, file_name=True) + ds = Dataset(filename) + np.testing.assert_array_equal(ds.variables['time'][:].data, [15., 45., 75., 105., 135., 165., 195., 225., 255., 285., 315., 345., 375., 405., 435., 465., 495., 525., 555., 585., 615., 645., 675., 705.]) + ds.close() + os.remove(filename) + + + def test_multiple_writes_with_leadtime(self): + # load MIP table + mip_table = "CMIP6_Amon_leadtime.json" + table_id = cmor.load_table(mip_table) + + # construct axes + time = cmor.axis(table_entry="time", units="days since 2000-01-01") + height2m = cmor.axis(table_entry="height2m", units="m", coord_vals=np.array((2.0,))) + latitude = cmor.axis(table_entry="latitude", units="degrees_north", + coord_vals=np.array(range(5)), + cell_bounds=np.array(range(6))) + longitude = cmor.axis(table_entry="longitude", units="degrees_east", + coord_vals=np.array(range(5)), + cell_bounds=np.array(range(6))) + reftime = cmor.axis(table_entry="reftime1", units="days since 2000-01-01", + coord_vals=np.array([10.0])) + axis_ids = [longitude, latitude, time, reftime, height2m] + + tas_var_id = cmor.variable(table_entry="tas", axis_ids=axis_ids, units="K") + # write data + for year in range(2): + data = np.random.random(25 * 12) + reshaped_data = data.reshape((5, 5, 12, 1, 1)) + time_vals = [pt * 30 + 360.0 * year + 15.0 for pt in range(12)] + time_bnds = [360.0 * year] + [pt * 30 + 360.0 * year + 30.0 for pt in range(12)] + cmor.write(tas_var_id, reshaped_data, time_vals=time_vals, time_bnds=time_bnds) + + filename = cmor.close(tas_var_id, file_name=True) + ds = Dataset(filename) + np.testing.assert_array_equal(ds.variables['time'][:].data, + [15., 45., 75., 105., 135., 165., 195., 225., 255., 285., 315., 345., 375., 405., + 435., 465., 495., 525., 555., 585., 615., 645., 675., 705.]) + ds.close() + os.remove(filename) + if __name__ == '__main__': unittest.main() diff --git a/TestTables/CMIP6_Amon.json b/TestTables/CMIP6_Amon.json new file mode 100644 index 00000000..63940517 --- /dev/null +++ b/TestTables/CMIP6_Amon.json @@ -0,0 +1,36 @@ +{ + "Header": { + "data_specs_version": "01.00.33", + "cmor_version": "3.5", + "table_id": "Table Amon", + "realm": "atmos atmosChem", + "table_date": "18 November 2020", + "missing_value": "1e20", + "int_missing_value": "-999", + "product": "model-output", + "approx_interval": "30.00000", + "generic_levels": "alevel alevhalf", + "mip_era": "CMIP6", + "Conventions": "CF-1.7 CMIP-6.2" + }, + "variable_entry": { + "tas": { + "frequency": "mon", + "modeling_realm": "atmos", + "standard_name": "air_temperature", + "units": "K", + "cell_methods": "area: time: mean", + "cell_measures": "area: areacella", + "long_name": "Near-Surface Air Temperature", + "comment": "near-surface (usually, 2 meter) air temperature", + "dimensions": "longitude latitude time height2m", + "out_name": "tas", + "type": "real", + "positive": "", + "valid_min": "", + "valid_max": "", + "ok_min_mean_abs": "", + "ok_max_mean_abs": "" + } + } +}