From b8ee36352d4d13c58819998b9ad4b6afa1058f63 Mon Sep 17 00:00:00 2001 From: Heiko Goelzer <35115552+hgoelzer@users.noreply.github.com> Date: Thu, 18 Jun 2026 19:06:35 +0200 Subject: [PATCH 1/3] Fix ST time encoding to use Jan 1 of year+1 per ISMIP7 convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit State variable (ST) timestamps now follow the ismip7-time-encoding reference: end-of-year snapshots are encoded as Jan 1 of the following year (e.g. simulation year 2015 → 2016-01-01), not Dec 31. Flux variable (FL) encoding is unchanged: mid-year timestamp Jul 1 with time bounds [Jan 1, Jan 1 next year]. experiments_ismip7.csv is redesigned to store nominal simulation years (start_year_min, start_year_max, end_year) instead of raw date strings. The checker derives expected FL/ST timestamps at runtime via the new _nominal_to_timestamp() helper, applying ±1 day tolerance for float32 encoding jitter. _check_naming() and _check_time() both receive var_type so the ST year offset (filename year N = timestamp year N+1) is handled correctly. The test suite and README are updated accordingly. --- README.md | 25 +++++++++++- compliance_checker.py | 66 +++++++++++++++++++++----------- experiments_ismip7.csv | 12 +++--- generate/generate_test_files.py | 14 ++++--- tests/test_compliance_checker.py | 4 +- 5 files changed, 83 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index ed7c1be..88d86f7 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,29 @@ Checks ISMIP7 NetCDF simulation datasets for compliance with the [ISMIP7 data re 1. **Naming** — variable name, region field, ISM member id (`mNNN`), ESM name (CMIP6/CMIP7 registry), forcing member id (`fNNN`), set counter (`[C|E|P]NNN`), and year range (`YYYY-YYYY` matching the actual time axis). 2. **Numerical** — units match the data request; all values lie within the allowed min/max range for the relevant region; array is not entirely fill values. 3. **Spatial** *(xyt variables only)* — grid corners lie within the expected AIS or GrIS extents; resolution is one of the allowed values; x and y cell size are equal. -4. **Time** — time dimension is present, unlimited, and monotonically increasing; annual cadence; experiment end date and duration match `experiments_ismip7.csv`. +4. **Time** — time dimension is present, unlimited, and monotonically increasing; annual cadence; experiment start/end dates and duration match `experiments_ismip7.csv`, accounting for the variable type (ST or FL, see [Time encoding](#time-encoding)). 5. **Attributes** — required global and coordinate attributes are present and have correct values; `standard_name` matches data request; `_FillValue` equals the NetCDF4 default for the variable's dtype; variable and time are float32; `scale_factor` and `add_offset` are not allowed. -Compliance criteria are defined in `conventions/ISMIP7_variable_request.xlsx` (variable metadata) and `experiments_ismip7.csv` (valid experiment date ranges). +Compliance criteria are defined in `conventions/ISMIP7_variable_request.xlsx` (variable metadata) and `experiments_ismip7.csv` (valid experiment year ranges and durations). + +--- + +## Time encoding + +ISMIP7 uses the standard (Gregorian) CF calendar with time recorded as **days since 1850-01-01 00:00:00**. The encoding convention differs by variable type: + +| Variable type | Time coordinate | Time bounds | +|---|---|---| +| **State (ST)** — snapshots | Jan 1 of year N+1 (= end of year N) | none | +| **Flux (FL)** — annual averages | Jul 1 of year N (mid-year) | Jan 1 of year N → Jan 1 of year N+1 | + +For example, a 286-year `ctrl` simulation covering nominal years 2015–2300: +- ST files carry timestamps `2016-01-01` … `2301-01-01` +- FL files carry timestamps `2015-07-01` … `2300-07-01`, with bounds `[2015-01-01, 2016-01-01]` … `[2300-01-01, 2301-01-01]` + +The filename year range (`YYYY-YYYY`) always refers to the **nominal simulation years** (2015–2300 in the example above), regardless of variable type. The checker accounts for this when validating the filename against the time axis. + +Reference lookup tables are available in the companion repository [`ismip7-time-encoding`](https://github.com/ismip/ismip7-time-encoding). --- @@ -43,6 +62,8 @@ python compliance_checker.py --source-path ./Models/GrIS/ISMIP7/SYNTH1/CORE --va | `--source-path` | `./Models/GrIS/ISMIP7/SYNTH1/CORE` | Directory containing `.nc` files to check | | `--variable-list` | `ismip7_scalars` | `ismip7_xyt`, `ismip7_scalars`, or `ismip7` (both) | +`experiments_ismip7.csv` defines the allowed nominal year ranges and durations for each experiment. The checker derives the expected FL and ST timestamps from these year values at runtime (see [Time encoding](#time-encoding)). + --- ## Generating synthetic test files diff --git a/compliance_checker.py b/compliance_checker.py index 1eef924..9877508 100755 --- a/compliance_checker.py +++ b/compliance_checker.py @@ -254,17 +254,27 @@ def _load_experiments_csv(file_path: str): for _, row in frame.iterrows(): experiments.append( { - "experiment": row["experiment"], - "startinf": datetime.datetime.strptime(row["startinf"], "%Y-%m-%d"), - "startsup": datetime.datetime.strptime(row["startsup"], "%Y-%m-%d"), - "endinf": datetime.datetime.strptime(row["endinf"], "%Y-%m-%d"), - "endsup": datetime.datetime.strptime(row["endsup"], "%Y-%m-%d"), - "duration": int(row["duration"]), + "experiment": row["experiment"], + "start_year_min": int(row["start_year_min"]), + "start_year_max": int(row["start_year_max"]), + "end_year": int(row["end_year"]), + "duration": int(row["duration"]), } ) return experiments +def _nominal_to_timestamp(year: int, var_type: str) -> datetime.datetime: + """Return the expected encoded timestamp for a nominal simulation year. + + ST variables: Jan 1 of the following year (end-of-year snapshot). + FL variables: Jul 1 of the same year (mid-year average). + """ + if var_type == "ST": + return datetime.datetime(year + 1, 1, 1) + return datetime.datetime(year, 7, 1) + + def _run_compliance_checker( source_path: str, commit_num: str, @@ -667,6 +677,7 @@ def _check_naming( dim: set, isscalar: bool, report_naming_issues: list, + var_type: str = "", ) -> int: errors = 0 @@ -745,8 +756,10 @@ def _check_naming( errors += 1 elif "time" in ds.coords: _decoded_time = xr.decode_cf(ds, decode_times=xr.coders.CFDatetimeCoder(use_cftime=True))["time"] - actual_start = min(_decoded_time).item().year - actual_end = max(_decoded_time).item().year + # ST timestamps are Jan 1 of year+1; subtract 1 to recover the nominal simulation year. + _yr_offset = 1 if var_type == "ST" else 0 + actual_start = min(_decoded_time).item().year - _yr_offset + actual_end = max(_decoded_time).item().year - _yr_offset if fn_start_year != actual_start: log_file.write( f" - ERROR: filename start year {fn_start_year} does not match first time step year {actual_start}.\n" @@ -896,6 +909,7 @@ def _check_time( dim: set, experiments: list, experiment_name: str, + var_type: str = "", ) -> int: errors = 0 @@ -977,6 +991,13 @@ def _check_time( end_exp.item().year, end_exp.item().month, end_exp.item().day ) + # Compute expected timestamp windows from nominal years + var_type (±1 day tolerance). + _tol = datetime.timedelta(days=1) + startinf = _nominal_to_timestamp(exp["start_year_min"], var_type) - _tol + startsup = _nominal_to_timestamp(exp["start_year_max"], var_type) + _tol + endinf = _nominal_to_timestamp(exp["end_year"], var_type) - _tol + endsup = _nominal_to_timestamp(exp["end_year"], var_type) + _tol + if exp["duration"] == -1: # Undetermined length: only require >= 1 year, start and end date in range if duration_years >= 1: @@ -994,7 +1015,7 @@ def _check_time( dateformat_start_exp = datetime.datetime( start_exp.item().year, start_exp.item().month, start_exp.item().day ) - if exp["startinf"] <= dateformat_start_exp <= exp["startsup"]: + if startinf <= dateformat_start_exp <= startsup: log_file.write( " - Experiment starts correctly on " + start_exp.item().strftime("%Y-%m-%d") @@ -1005,14 +1026,14 @@ def _check_time( " - ERROR: the experiment starts the " + start_exp.item().strftime("%Y-%m-%d") + ". The date should be comprised between " - + exp["startinf"].strftime("%Y-%m-%d") + + startinf.strftime("%Y-%m-%d") + " and " - + exp["startsup"].strftime("%Y-%m-%d") + + startsup.strftime("%Y-%m-%d") + "\n" ) errors += 1 - if exp["endinf"] <= dateformat_end_exp <= exp["endsup"]: + if endinf <= dateformat_end_exp <= endsup: log_file.write( " - Experiment ends correctly on " + end_exp.item().strftime("%Y-%m-%d") @@ -1023,9 +1044,9 @@ def _check_time( " - ERROR: the experiment ends on " + end_exp.item().strftime("%Y-%m-%d") + ". The date should be comprised between " - + exp["endinf"].strftime("%Y-%m-%d") + + endinf.strftime("%Y-%m-%d") + " and " - + exp["endsup"].strftime("%Y-%m-%d") + + endsup.strftime("%Y-%m-%d") + "\n" ) errors += 1 @@ -1037,7 +1058,7 @@ def _check_time( start_exp.item().month, start_exp.item().day, ) - if exp["startinf"] <= dateformat_start_exp <= exp["startsup"]: + if startinf <= dateformat_start_exp <= startsup: log_file.write( " - Experiment starts correctly on " + start_exp.item().strftime("%Y-%m-%d") @@ -1048,14 +1069,14 @@ def _check_time( " - ERROR: the experiment starts the " + start_exp.item().strftime("%Y-%m-%d") + ". The date should be comprised between " - + exp["startinf"].strftime("%Y-%m-%d") + + startinf.strftime("%Y-%m-%d") + " and " - + exp["startsup"].strftime("%Y-%m-%d") + + startsup.strftime("%Y-%m-%d") + "\n" ) errors += 1 - if exp["endinf"] <= dateformat_end_exp <= exp["endsup"]: + if endinf <= dateformat_end_exp <= endsup: log_file.write( " - Experiment ends correctly on " + end_exp.item().strftime("%Y-%m-%d") @@ -1066,9 +1087,9 @@ def _check_time( " - ERROR: the experiment ends on " + end_exp.item().strftime("%Y-%m-%d") + ". The date should be comprised between " - + exp["endinf"].strftime("%Y-%m-%d") + + endinf.strftime("%Y-%m-%d") + " and " - + exp["endsup"].strftime("%Y-%m-%d") + + endsup.strftime("%Y-%m-%d") + "\n" ) errors += 1 @@ -1295,8 +1316,9 @@ def _run_variable_checks( index = ismip_var.index(considered_variable) isscalar = ismip_meta[index]["dim"] == "t" + var_type = ismip_meta[index].get("type", "") - n_err = _check_naming(log_file, ds, file_name, region, dim, isscalar, report_naming_issues) + n_err = _check_naming(log_file, ds, file_name, region, dim, isscalar, report_naming_issues, var_type) var_naming_errors += n_err if n_err > 0: return var_naming_errors, var_num_errors, var_spatial_errors, var_time_errors, var_attr_errors @@ -1315,7 +1337,7 @@ def _run_variable_checks( if not isscalar: var_spatial_errors += _check_spatial(log_file, ds, grid_extent, possible_resolution) - var_time_errors += _check_time(log_file, ds, dim, experiments, experiment_name) + var_time_errors += _check_time(log_file, ds, dim, experiments, experiment_name, ismip_meta[var_index].get("type", "")) var_attr_errors += _check_attributes(log_file, ds, ivar, ismip_meta, var_index, isscalar, ismip_meta[var_index].get("type", ""), region) diff --git a/experiments_ismip7.csv b/experiments_ismip7.csv index 8b55547..ecaa1d6 100644 --- a/experiments_ismip7.csv +++ b/experiments_ismip7.csv @@ -1,6 +1,6 @@ -experiment;startinf;startsup;endinf;endsup;duration -historical;1850-01-01;2013-12-31;2014-06-30;2015-01-01;-1 -ssp370;2015-01-01;2016-01-02;2100-06-30;2101-01-01;86 -ssp126;2015-01-01;2016-01-02;2300-06-30;2301-01-01;286 -ssp585;2015-01-01;2016-01-02;2300-06-30;2301-01-01;286 -ctrl;2015-01-01;2016-01-02;2300-06-30;2301-01-01;286 +experiment;start_year_min;start_year_max;end_year;duration +historical;1850;2014;2014;-1 +ssp370;2015;2015;2100;86 +ssp126;2015;2015;2300;286 +ssp585;2015;2015;2300;286 +ctrl;2015;2015;2300;286 diff --git a/generate/generate_test_files.py b/generate/generate_test_files.py index 897c8c8..348a827 100644 --- a/generate/generate_test_files.py +++ b/generate/generate_test_files.py @@ -319,12 +319,12 @@ def create_netcdf_file(output_file, grid_name='GrIS_16000m', scenario='ctrl', st # Select appropriate time coordinate (days since 1850, calendar: standard) origin = datetime(1850, 1, 1).date() if var_type == 'ST': - # End of year (Dec 31) + # End of year: Jan 1 of next year (ISMIP7 convention) time_days = [] time_bounds = None for i in range(nyears): year = start_year + i - dt = datetime(year, 12, 31).date() + dt = datetime(year + 1, 1, 1).date() time_days.append(float((dt - origin).days)) time_coord = np.array(time_days, dtype=np.float32) elif var_type == 'FL': @@ -341,12 +341,12 @@ def create_netcdf_file(output_file, grid_name='GrIS_16000m', scenario='ctrl', st time_bounds[i, 1] = float((t1 - origin).days) time_coord = np.array(time_days, dtype=np.float32) else: - # Default to state variable timing (end of year) + # Default to state variable timing (Jan 1 of next year) time_days = [] time_bounds = None for i in range(nyears): year = start_year + i - dt = datetime(year, 12, 31).date() + dt = datetime(year + 1, 1, 1).date() time_days.append(float((dt - origin).days)) time_coord = np.array(time_days, dtype=np.float32) @@ -459,11 +459,12 @@ def create_netcdf_file(output_file, grid_name='GrIS_16000m', scenario='ctrl', st # Select appropriate time coordinate (days since 1850) origin = datetime(1850, 1, 1).date() if var_type == 'ST': + # End of year: Jan 1 of next year (ISMIP7 convention) time_days = [] time_bounds = None for i in range(nyears): year = start_year + i - dt = datetime(year, 12, 31).date() + dt = datetime(year + 1, 1, 1).date() time_days.append(float((dt - origin).days)) time_coord = np.array(time_days, dtype=np.float32) elif var_type == 'FL': @@ -479,11 +480,12 @@ def create_netcdf_file(output_file, grid_name='GrIS_16000m', scenario='ctrl', st time_bounds[i, 1] = float((t1 - origin).days) time_coord = np.array(time_days, dtype=np.float32) else: + # Default to state variable timing (Jan 1 of next year) time_days = [] time_bounds = None for i in range(nyears): year = start_year + i - dt = datetime(year, 12, 31).date() + dt = datetime(year + 1, 1, 1).date() time_days.append(float((dt - origin).days)) time_coord = np.array(time_days, dtype=np.float32) diff --git a/tests/test_compliance_checker.py b/tests/test_compliance_checker.py index 399c2ba..2609866 100644 --- a/tests/test_compliance_checker.py +++ b/tests/test_compliance_checker.py @@ -154,8 +154,8 @@ def test_checker_reports_historical_time_range_violation(case_dir): set_time_values( target_file, [ - datetime(2015, 12, 31), - datetime(2016, 12, 31), + datetime(2016, 1, 1), + datetime(2017, 1, 1), ], ) rename_file_part( From a01f5049fb56e49b70b5c8b51b3c37d0dce47dd6 Mon Sep 17 00:00:00 2001 From: Heiko Goelzer <35115552+hgoelzer@users.noreply.github.com> Date: Fri, 19 Jun 2026 09:44:15 +0200 Subject: [PATCH 2/3] Replace ISMIP7_variable_request.xlsx with a plain-text CSV The variable request criteria are now stored in conventions/ISMIP7_variable_request.csv instead of an Excel file, making the data diff-friendly and human-readable in version control. Both compliance_checker.py and generate/generate_test_files.py are updated to use pd.read_csv(); openpyxl is removed from isschecker_env.yml. README, generate/README, and CLAUDE.md are updated to reflect the new file name and dropped dependency. --- README.md | 4 ++-- compliance_checker.py | 10 +++++----- generate/README.md | 2 +- generate/generate_test_files.py | 28 ++++++++++++++-------------- isschecker_env.yml | 1 - 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 88d86f7..bb0d400 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Checks ISMIP7 NetCDF simulation datasets for compliance with the [ISMIP7 data re 4. **Time** — time dimension is present, unlimited, and monotonically increasing; annual cadence; experiment start/end dates and duration match `experiments_ismip7.csv`, accounting for the variable type (ST or FL, see [Time encoding](#time-encoding)). 5. **Attributes** — required global and coordinate attributes are present and have correct values; `standard_name` matches data request; `_FillValue` equals the NetCDF4 default for the variable's dtype; variable and time are float32; `scale_factor` and `add_offset` are not allowed. -Compliance criteria are defined in `conventions/ISMIP7_variable_request.xlsx` (variable metadata) and `experiments_ismip7.csv` (valid experiment year ranges and durations). +Compliance criteria are defined in `conventions/ISMIP7_variable_request.csv` (variable metadata) and `experiments_ismip7.csv` (valid experiment year ranges and durations). --- @@ -38,7 +38,7 @@ conda env create -f isschecker_env.yml conda activate isschecker ``` -Dependencies: Python 3.14, `numpy` 2.4, `pandas` 3.0, `openpyxl` 3.1, `xarray` 2026.4, `cftime` 1.6, `netCDF4` 1.7, `tqdm` 4.67. +Dependencies: Python 3.14, `numpy` 2.4, `pandas` 3.0, `xarray` 2026.4, `cftime` 1.6, `netCDF4` 1.7, `tqdm` 4.67. --- diff --git a/compliance_checker.py b/compliance_checker.py index 9877508..85fad6b 100755 --- a/compliance_checker.py +++ b/compliance_checker.py @@ -54,7 +54,7 @@ DEFAULT_VARIABLE_LIST = "ismip7_scalars" VARIABLE_LIST_CHOICES = ("ismip7_scalars", "ismip7_xyt", "ismip7") -VARIABLE_REQUEST_XLSX = os.path.join("conventions", "ISMIP7_variable_request.xlsx") +VARIABLE_REQUEST_CSV = os.path.join("conventions", "ISMIP7_variable_request.csv") EXPERIMENTS_ISMIP7_CSV_FILENAME = "experiments_ismip7.csv" @@ -162,7 +162,7 @@ def run_checker( ismip_var=ismip_var, mandatory_variables=mandatory_variables, experiments=experiments_ismip7, - criteria_file=VARIABLE_REQUEST_XLSX, + criteria_file=VARIABLE_REQUEST_CSV, ) log_path = os.path.join(source_path, "compliance_checker_log.txt") @@ -206,13 +206,13 @@ def _parse_args() -> argparse.Namespace: def _load_criteria(workdir: str, variable_list: str): - excel_path = os.path.join(workdir, VARIABLE_REQUEST_XLSX) + csv_path = os.path.join(workdir, VARIABLE_REQUEST_CSV) try: - df = pd.read_excel(excel_path, sheet_name="ISM") + df = pd.read_csv(csv_path) except IOError: print( "ERROR: Unable to open the variable request file. Is the path correct? " - + excel_path + + csv_path ) raise diff --git a/generate/README.md b/generate/README.md index 1818f12..ed482c8 100644 --- a/generate/README.md +++ b/generate/README.md @@ -65,6 +65,6 @@ python generate/generate_test_files.py --grid GrIS_16000m --scenario ctrl \ ## Notes -- Variable metadata is read from `conventions/ISMIP7_variable_request.xlsx`; grid definitions from the top-level `gdfs/` directory. +- Variable metadata is read from `conventions/ISMIP7_variable_request.csv`; grid definitions from the top-level `gdfs/` directory. - `group`, `model`, `contact_name`, and `contact_email` are hardcoded in `create_netcdf_file()` to synthetic defaults — edit there to customise. - Generated files are synthetic and intended for testing the compliance checker, not for scientific use. diff --git a/generate/generate_test_files.py b/generate/generate_test_files.py index 348a827..828cf57 100644 --- a/generate/generate_test_files.py +++ b/generate/generate_test_files.py @@ -84,14 +84,14 @@ def parse_grid_file(gdf_file): return grid_params -def read_variable_criteria(excel_file, include_non_mandatory=False): +def read_variable_criteria(csv_file, include_non_mandatory=False): """ - Read variable criteria from Excel file. + Read variable criteria from CSV file. Parameters ---------- - excel_file : str - Path to the Excel file + csv_file : str + Path to the CSV file include_non_mandatory : bool Whether to include non-mandatory variables @@ -104,8 +104,8 @@ def read_variable_criteria(excel_file, include_non_mandatory=False): variables = {} - # Read the Excel file - df = pd.read_excel(excel_file, sheet_name='ISM') + # Read the CSV file + df = pd.read_csv(csv_file) # Filter out rows that don't have variable names df = df.dropna(subset=['Variable Name']) @@ -128,13 +128,13 @@ def read_variable_criteria(excel_file, include_non_mandatory=False): variables[var_name] = { 'dimensions': dimensions, 'type': row['Type'], - 'description': row['long_name'], # Use long_name from Excel + 'description': row['long_name'], # Use long_name from CSV 'standard_name': row['standard_name'] if pd.notna(row['standard_name']) else '', 'units': str(row['units']) if pd.notna(row['units']) else '', 'mandatory': row['Mandatory (yes/no)'].lower() == 'yes', } - # Collect any min_/max_ columns (case-insensitive) from the Excel sheet + # Collect any min_/max_ columns (case-insensitive) from the CSV # Normalize keys to lowercase (e.g., 'min_gris', 'max_ais') and store for col in df.columns: try: @@ -261,13 +261,13 @@ def create_netcdf_file(output_file, grid_name='GrIS_16000m', scenario='ctrl', st xinc = grid_params['xinc'] yinc = grid_params['yinc'] - # Read variable criteria from Excel file - excel_file = conventions_dir / 'ISMIP7_variable_request.xlsx' - if not excel_file.exists(): - print(f"Warning: {excel_file} not found") + # Read variable criteria from CSV file + csv_file = conventions_dir / 'ISMIP7_variable_request.csv' + if not csv_file.exists(): + print(f"Warning: {csv_file} not found") variables = {} else: - variables = read_variable_criteria(str(excel_file), include_non_mandatory) + variables = read_variable_criteria(str(csv_file), include_non_mandatory) # Create coordinate arrays x = np.arange(nx, dtype=np.float32) * xinc + xfirst @@ -688,7 +688,7 @@ def create_multiple_files(output_dir=None, n_files=3, conventions_dir=None, parser.add_argument( '--include-non-mandatory', action='store_true', - help='Include non-mandatory variables from the ISM Excel variable list' + help='Include non-mandatory variables from the ISM CSV variable list' ) parser.add_argument( '--multiple', diff --git a/isschecker_env.yml b/isschecker_env.yml index 7e8a849..2823a79 100644 --- a/isschecker_env.yml +++ b/isschecker_env.yml @@ -8,7 +8,6 @@ dependencies: # Core scientific python - numpy=2.4.3 - pandas=3.0.2 - - openpyxl=3.1.5 - pytest=8.4.2 - tqdm=4.67.3 From d65c5185af891358c7ddf6336ea6e414a88a169b Mon Sep 17 00:00:00 2001 From: Heiko Goelzer <35115552+hgoelzer@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:05:22 +0200 Subject: [PATCH 3/3] Add csv file with updated ranges and corrected sign on licalvf and lifmassbf --- conventions/ISMIP7_variable_request.csv | 40 +++++++++++++++++++++++ conventions/ISMIP7_variable_request.xlsx | Bin 12398 -> 0 bytes 2 files changed, 40 insertions(+) create mode 100644 conventions/ISMIP7_variable_request.csv delete mode 100644 conventions/ISMIP7_variable_request.xlsx diff --git a/conventions/ISMIP7_variable_request.csv b/conventions/ISMIP7_variable_request.csv new file mode 100644 index 0000000..22e8cd4 --- /dev/null +++ b/conventions/ISMIP7_variable_request.csv @@ -0,0 +1,40 @@ +long_name,Dim,Type,Variable Name,standard_name,units,Mandatory (yes/no),Comment,min_value_ais,max_value_ais,min_value_gris,max_value_gris +Ice thickness,"x,y,t",ST,lithk,land_ice_thickness,m,yes,Ice thickness of the ice sheet,0,5000,0,5000 +Surface elevation,"x,y,t",ST,orog,surface_altitude,m,yes,Surface elevation of the ice sheet,0,4500,0,4500 +Bedrock elevation,"x,y,t",ST,topg,bedrock_altitude,m,yes,The bedrock topography (may change during the projections),-7000,4000,-4000,4000 +Ice base elevation,"x,y,t",ST,base,,m,yes,Should get standard name land_ice_temperature,-4000,4000,-4000,4000 +Geothermal heat flux,"x,y,t",FL,hfgeoubed,upward_geothermal_heat_flux_in_land_ice,W m-2,no,Geothermal Heat flux at the ice interface,0,0.3,0,0.3 +Surface mass balance flux,"x,y,t",FL,acabf,land_ice_surface_specific_mass_balance_flux,kg m-2 s-1,yes,Surface Mass Balance flux,-0.0006,0.001,-0.0006,0.001 +Basal mass balance flux beneath grounded ice,"x,y,t",FL,libmassbfgr,land_ice_basal_specific_mass_balance_flux,kg m-2 s-1,yes,Basal mass balance flux (only beneath grounded ice),-0.0003,0.0001,-0.0003,0.0001 +Basal mass balance flux beneath floating ice,"x,y,t",FL,libmassbffl,land_ice_basal_specific_mass_balance_flux,kg m-2 s-1,yes,Basal mass balance flux (only beneath floating ice),-0.008,0.001,-0.008,0.001 +Ice thickness imbalance,"x,y,t",FL,dlithkdt,tendency_of_land_ice_thickness,m s-1,yes,dHdt,-0.0001,0.0001,-0.0001,0.0001 +Surface velocity in x,"x,y,t",ST,xvelsurf,land_ice_surface_x_velocity,m s-1,no,u-velocity at land ice surface,-0.0004,0.0004,-0.0008,0.0008 +Surface velocity in y,"x,y,t",ST,yvelsurf,land_ice_surface_y_velocity,m s-1,no,v-velocity at land ice surface,-0.0004,0.0004,-0.0008,0.0008 +Surface velocity in z,"x,y,t",ST,zvelsurf,land_ice_surface_upward_velocity,m s-1,no,w-velocity at land ice surface,-4.00E-06,4e-06,-8.00E-06,8e-06 +Basal velocity in x,"x,y,t",ST,xvelbase,land_ice_basal_x_velocity,m s-1,no,u-velocity at land ice base,-0.0004,0.0004,-0.0008,0.0008 +Basal velocity in y,"x,y,t",ST,yvelbase,land_ice_basal_y_velocity,m s-1,no,v-velocity at land ice base,-0.0004,0.0004,-0.0008,0.0008 +Basal velocity in z,"x,y,t",ST,zvelbase,land_ice_basal_upward_velocity,m s-1,no,w-velocity at land ice base,-5.00E-06,5e-06,-8.00E-06,8e-06 +Mean velocity in x,"x,y,t",ST,xvelmean,land_ice_vertical_mean_x_velocity,m s-1,yes,Vertical mean land ice velocity ,-0.0004,0.0004,-0.0008,0.0008 +Mean velocity in y,"x,y,t",ST,yvelmean,land_ice_vertical_mean_y_velocity,m s-1,yes,The vertical mean land ice velocity is the average from the bedrock to the surface of the ice,-0.0004,0.0004,-0.0008,0.0008 +Surface temperature,"x,y,t",ST,litemptop,temperature_at_top_of_ice_sheet_model,K,no,Ice temperature at surface,183,290,183,290 +Depth average temperature,"x,y,t",ST,litempavg,,K,no,Should get standard name land_ice_temperature,183,290,183,290 +Vertical Basal temperature gradient beneath grounded ice sheet,"x,y,t",ST,litempgradgr,,K m-1,no,"Vertical Basal temperature gradient beneath grounded ice sheet, should get a standard name",-10,10,-10,10 +Vertical Basal temperature gradient beneath floating ice shelf,"x,y,t",ST,litempgradfl,,K m-1,no,"Vertical Basal temperature gradient beneath floating ice shelf, should get a standard name",-10,10,-10,10 +Basal temperature beneath grounded ice sheet,"x,y,t",ST,litempbotgr,temperature_at_base_of_ice_sheet_model,K,no,Ice temperature at base of grounded ice sheet,183,290,183,290 +Basal temperature beneath floating ice shelf,"x,y,t",ST,litempbotfl,temperature_at_base_of_ice_sheet_model,K,no,Ice temperature at base of floating ice shelf,183,290,183,290 +Basal drag,"x,y,t",ST,strbasemag,land_ice_basal_drag,Pa,yes,Basal drag,0,300000,0,300000 +Calving flux,"x,y,t",FL,licalvf,land_ice_specific_mass_flux_due_to_calving,kg m-2 s-1,yes,Ice mass change resulting from iceberg calving. Only for grid cells in contact with ocean,-1E+11,0,-1E+11,0 +Ice front melt flux,"x,y,t",FL,lifmassbf,,kg m-2 s-1,yes,Ice mass change resulting from ice front melting. Only for grid cells in contact with ocean. Should get standard name: land_ice_specific_mass_flux_due_to_ice_front_melting,-1E+11,0,-1E+11,0 +Land ice area fraction,"x,y,t",ST,sftgif,land_ice_area_fraction,1,yes,"Fraction of grid cell covered by land ice (ice sheet, ice shelf, ice cap, glacier)",0,1,0,1 +Grounded ice sheet area fraction,"x,y,t",ST,sftgrf,grounded_ice_sheet_area_fraction,1,yes,"Fraction of grid cell covered by grounded ice sheet, where grounded indicates that the quantity correspond to the ice sheet that flows over bedrock",0,1,0,1 +Floating ice sheet area fraction,"x,y,t",ST,sftflf,floating_ice_shelf_area_fraction,1,yes,Fraction of grid cell covered by ice sheet flowing over seawater,0,1,0,1 +Total ice mass,t,ST,lim,land_ice_mass,kg,yes,"spatial integration, volume times density",0,1e+25,0,1e+25 +Mass above floatation,t,ST,limnsw,land_ice_mass_not_displacing_sea_water,kg,yes,"spatial integration, volume times density",0,1e+25,0,1e+25 +Grounded ice area,t,ST,iareagr,grounded_ice_sheet_area,m^2,yes,spatial integration,0,1e+25,0,1e+25 +Floating ice area,t,ST,iareafl,floating_ice_shelf_area,m^2,yes,spatial integration,0,1e+25,0,1e+25 +Total SMB flux,t,FL,tendacabf,tendency_of_land_ice_mass_due_to_surface_mass_balance,kg s-1,yes,spatial integration,0,1e+25,0,1e+25 +Total BMB flux beneath grounded ice,t,FL,tendlibmassbfgr,tendency_of_land_ice_mass_due_to_basal_mass_balance,kg s-1,yes,spatial integration,0,1e+25,0,1e+25 +Total BMB flux beneath floating ice,t,FL,tendlibmassbffl,tendency_of_land_ice_mass_due_to_basal_mass_balance,kg s-1,yes,spatial integration (computed beneath floating ice only),0,1e+25,0,1e+25 +Total calving flux,t,FL,tendlicalvf,tendency_of_land_ice_mass_due_to_calving,kg s-1,yes,spatial integration,0,1e+25,0,1e+25 +Total ice front melting flux,t,FL,tendlifmassbf,,kg s-1,yes,"spatial integration, should get standard name: tendency_of_land_ice_mass_due_to_ice_front_melting",0,1e+25,0,1e+25 +Total grounding line flux,t,FL,tendligroundf,,kg s-1,yes,"spatial integration, should get a standard name",0,1e+25,0,1e+25 \ No newline at end of file diff --git a/conventions/ISMIP7_variable_request.xlsx b/conventions/ISMIP7_variable_request.xlsx deleted file mode 100644 index 92a42d4f8ad2a7b2fe521693813de112779a840a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12398 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 D{lhyj