diff --git a/doc/sphinx/source/recipes/figures/steric_patterns/detrended.png b/doc/sphinx/source/recipes/figures/steric_patterns/detrended.png new file mode 100644 index 0000000000..52a8e7021b Binary files /dev/null and b/doc/sphinx/source/recipes/figures/steric_patterns/detrended.png differ diff --git a/doc/sphinx/source/recipes/figures/steric_patterns/predictions.png b/doc/sphinx/source/recipes/figures/steric_patterns/predictions.png new file mode 100644 index 0000000000..081fd47a43 Binary files /dev/null and b/doc/sphinx/source/recipes/figures/steric_patterns/predictions.png differ diff --git a/doc/sphinx/source/recipes/figures/steric_patterns/regressions.png b/doc/sphinx/source/recipes/figures/steric_patterns/regressions.png new file mode 100644 index 0000000000..005f294471 Binary files /dev/null and b/doc/sphinx/source/recipes/figures/steric_patterns/regressions.png differ diff --git a/doc/sphinx/source/recipes/index.rst b/doc/sphinx/source/recipes/index.rst index 77a9815107..ba90c3b44c 100644 --- a/doc/sphinx/source/recipes/index.rst +++ b/doc/sphinx/source/recipes/index.rst @@ -137,6 +137,7 @@ Ocean recipe_enso_clivar recipe_oceans recipe_sea_surface_salinity + recipe_steric_patterns recipe_russell18jgr Other diff --git a/doc/sphinx/source/recipes/recipe_steric_patterns.rst b/doc/sphinx/source/recipes/recipe_steric_patterns.rst new file mode 100644 index 0000000000..0421af0c67 --- /dev/null +++ b/doc/sphinx/source/recipes/recipe_steric_patterns.rst @@ -0,0 +1,127 @@ +.. _recipes_steric_patterns: + +Calculate sterodynamic sea-level patterns from CMIP6 models +=========================================================== + +Overview +-------- + +The recipe recipe_steric_patterns calculates sterodynamic sea-level change +patterns from CMIP6 model datasets. Patterns are calculated for the SSP2-4.5, +SSP3-7.0 and SSP5-8.5 scenarios. + +These patterns are useful because we can emulate spatially-resolved +sterodynamic sea-level rise from global thermal expansion at a fraction of the +computational cost of running a fully-coupled Earth system model or ocean model. + +The methods for this recipe are derived from those in Palmer et al. (2020) which +themselves are rooted in the IPCC AR5 report. + +.. note:: + The regrid setting in the recipe is set to a (180, 360) grid to put + all models on the same grid. + + +Available recipes and diagnostics +--------------------------------- + +Recipes are stored in esmvaltool/recipes/ + +* recipe_steric_patterns.yml + +Diagnostics are stored in esmvaltool/diag_scripts/steric_patterns/ + +* steric_patterns.py: generates sterodynamic patterns from input datasets +* sub_funcs.py: set of sub functions to assist with the main script + + +User settings in recipe +----------------------- + +#. Script steric_patterns.py + + *Required settings for script* + + None + + *Optional settings for script* + + None + + *Required settings for variables* + + * short_name + * additional_datasets + * exp + + *Optional settings for variables* + + None + + *Required settings for preprocessor* + + * regular_grid: regrids data to a (180, 360) grid + + *Optional settings for preprocessor* + + None + + +Variables +--------- + +#. Script steric_patterns.py + +* zostoga (ocean, monthly, time) +* zos (ocean, monthly, longitude, latitude, time) + + +Observations and reformat scripts +--------------------------------- + +None + + +References +---------- + +* Palmer, M. D., Gregory, J. M., Bagge, M., Calvert, D., Hagedoorn, J. M., + & Howard, T., et al. (2020). Exploring the drivers of global and local + sea-level change over the 21st century and beyond. Earth's Future, 8, + e2019EF001413. https://doi.org/10.1029/2019EF001413 + +* Perks, R., & Weeks, J. (2023). MetOffice/ProFSea-tool: v1.0.0 (v1.0.0). + Zenodo. https://doi.org/10.5281/zenodo.10255468 + + +Example plots +------------- + +.. _fig_steric_patterns_1: +.. figure:: /recipes/figures/steric_patterns/detrended.png + :align: center + :width: 80% + + Detrended zostoga, correcting for model drift using the pre-industrial + (PiControl) experiment following Palmer et al., (2020). This is done for + each model and scenario. + +.. _fig_steric_patterns_2: +.. figure:: /recipes/figures/steric_patterns/regressions.png + :align: center + :width: 80% + + Example of the regressions between the global thermal expansion (zostoga) and + local dynamic sea-level height (zos) for three random grid-cells. The coordinate + for each of the grid-cells is shown in the top-right corner of each panel. + +.. _fig_steric_patterns_3: +.. figure:: /recipes/figures/steric_patterns/predictions.png + :align: center + :width: 80% + + Example end-of-century predictions from the UKESM1-0-LL model patterns + for each SSP, as well as a timeseries of globally-averaged mean-squared + error. Two polar artifacts can be seen in each map panel, seemingly + occuring due to ESMValTool's regridding functionality. This seems to occur + more often on low-resolution models as opposed to high-res. diff --git a/esmvaltool/diag_scripts/steric_patterns/__init__.py b/esmvaltool/diag_scripts/steric_patterns/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esmvaltool/diag_scripts/steric_patterns/steric_patterns.py b/esmvaltool/diag_scripts/steric_patterns/steric_patterns.py new file mode 100644 index 0000000000..d2339c2298 --- /dev/null +++ b/esmvaltool/diag_scripts/steric_patterns/steric_patterns.py @@ -0,0 +1,546 @@ +# (C) Crown Copyright 2025, Met Office. +"""Diagnostic script to calculate patterns of sterodynamic sea-level change from CMIP6 models. + +Description +----------- +Calculates regressions between the global thermal expansion (zostoga) and the +dynamic sea-level change (zos) for the CMIP6 models. This gives you patterns +of steric sea-level change for the different scenarios. + +Author +------ +Gregory Munday (Met Office, UK) +""" + +import logging +from pathlib import Path + +import cartopy.crs as ccrs +import iris +import iris.coord_categorisation +import iris.cube +import matplotlib.pyplot as plt +import numpy as np +from sklearn.linear_model import LinearRegression + +from esmvaltool.diag_scripts.shared import ProvenanceLogger, run_diagnostic +from esmvaltool.diag_scripts.steric_patterns import sub_funcs as sf + +logger = logging.getLogger(Path(__file__).stem) + + +def get_provenance_record() -> dict: + """Create a provenance record describing the diagnostic data and plot. + + Parameters + ---------- + None + + Returns + ------- + record : dict + provenance record + """ + return { + "caption": ["Calculating sterodynamic sea-level patterns"], + "statistics": ["mean", "other"], + "domains": ["global"], + "themes": ["phys"], + "realms": ["ocean"], + "authors": ["munday_gregory"], + "plot_types": ["scatter", "map"], + "references": ["palmer2020", "perks2023"], + } + + +def calculate_drift(cube: iris.cube.Cube) -> float: + """Calculate zostoga model drift over the PiControl experiment. + + Parameters + ---------- + cube : iris.cube.Cube + cube of global thermal expansion + + Returns + ------- + drift : float + model drift (slope of regression) + """ + model = LinearRegression(fit_intercept=True) + model.fit(cube.coord("time").points.reshape(-1, 1), cube.data) + return model.coef_ + + +def detrend_zostoga( + zostoga: iris.cube.Cube, drift_coef: float, plot_path: Path +) -> iris.cube.Cube: + """Detrend the zostoga cube. + + Parameters + ---------- + zostoga: iris.cube.Cube + raw zostoga cube + drift_coef: float + drift coefficient to detrend zostoga + plot_path: Path + path to plot folder + + Returns + ------- + zostoga_dedrended: iris.cube.Cube + detrended zostoga cube + """ + trend = drift_coef * zostoga.coord("time").points + trend -= trend[0] + zostoga_detrended = zostoga - trend + + # Check how the detrending looks + fig = plt.figure(figsize=(10, 6)) + + ax = fig.add_subplot(111) + ax.scatter( + np.arange(len(zostoga.data)), + zostoga.data, + s=2, + alpha=0.8, + color="navy", + label="Scenario run (without drift correction)", + ) + ax.scatter( + np.arange(len(zostoga.data)), + zostoga_detrended.data, + s=2, + alpha=0.8, + color="darkorchid", + label="Scenario run (with drift correction)", + ) + ax.set_xlabel("Scenario time (months)") + ax.set_ylabel("Global thermal expansion (m)") + ax.legend(loc="upper left", frameon=False) + + fig.savefig( + Path(plot_path) / f"detrended_{zostoga.attributes['source_id']}.png", + dpi=150, + ) + plt.close() + return zostoga_detrended + + +def dyn_steric_regression( + zostoga: iris.cube.Cube, + zos: iris.cube.Cube, + plot_path: Path, + scenario: str, +) -> tuple[np.array]: + """Calculate the zostoga/zos regression. + + Parameters + ---------- + zostoga: iris.cube.Cube + zostoga cube + zos: iris.cube.Cube + zos cube + plot_path: Path + path to save plots to + scenario: str + scenario name + + Returns + ------- + slopes: np.array + regression slopes over the grid + mask: np.array + model-specific mask + """ + time = zos.coord("time") + dates = time.units.num2date(time.points) + yrs = np.array([date.year for date in dates]) + start_yr = 2005 + end_yr = 2100 + + # Calculate regression coefficients for period 2005-2100 + index = (yrs >= start_yr) & (yrs <= end_yr) + zostoga = zostoga[index] + zos = zos[index] + + zostoga.data = zostoga.data - np.mean(zostoga.data[0:10]) + zos.data = zos.data - np.mean(zos.data[0:10], axis=0) + + # Calculate the slope and intercepts of linear fits of the + # global and local sea level projections + regr = LinearRegression() + regr.fit( + zostoga.data.reshape(-1, 1), + zostoga.data.reshape(-1, 1) + zos.data.reshape(zos.data.shape[0], -1), + ) + slopes = regr.coef_.reshape(180, 360) + + # Deal with dodgy land mask, which won't really matter in the end anyway + if zos.attributes["source_id"] == "GISS-E2-1-H": + mask = np.full(zos.data.shape, np.nan) + else: + mask = zos.data.mask[0] + + fig = evaluate_regression(zostoga.data, zos.data, slopes, zos) + fig.savefig( + Path(plot_path) + / f"regression_{zostoga.attributes['source_id']}_{scenario}.png", + dpi=150, + ) + plt.close() + return slopes, mask + + +def save_data( + slopes: np.array, + mask: np.array, + work_path: Path, + model: str, + scenario: str, +) -> None: + """Save the zostoga/zos regression slopes and model mask. + + Parameters + ---------- + slopes: np.array + zostoga/zos regression slopes + mask: np.array + model mask + work_path: Path + path to save data to + model: str + model name + scenario: str + scenario name + + Returns + ------- + None + """ + np.save(Path(work_path) / f"zos_regression_{scenario}_{model}.npy", slopes) + np.save(Path(work_path) / f"zos_mask_{scenario}_{model}.npy", mask) + + +def evaluate_regression( + zostoga_data: np.array, + zos_data: np.array, + slopes: np.array, + zos_cube: iris.cube.Cube, +) -> plt.figure: + """Evaluate the regression. + + Parameters + ---------- + zostoga_data: np.array + global thermal expansion data + zos_data: np.array + dynamic sea-level change data + slopes: np.array + regression slopes + zos_cube: iris.cube.Cube + dynamic sea-level cube + + Returns + ------- + fig: plt.figure + figure of the regression + """ + x_vals = np.linspace( + zostoga_data.min(), zostoga_data.max(), len(zostoga_data) + ) + + # Plot the regression at a few grid points + fig = plt.figure(figsize=(12, 6), layout="constrained") + + lat_idxs = [150, 26, 170] + lon_idxs = [120, 30, 340] + lats = zos_cube.coord("latitude").points[lat_idxs] + lons = zos_cube.coord("longitude").points[lon_idxs] + for index in range(3): + ax = fig.add_subplot(1, 3, index + 1) + ax.scatter( + zostoga_data, + zostoga_data + zos_data[:, lat_idxs[index], lon_idxs[index]], + s=2, + alpha=0.8, + color="navy", + label="Model", + ) + ax.plot( + x_vals, + slopes[lat_idxs[index], lon_idxs[index]] * x_vals, + color="darkorchid", + label="Regression", + ) + ax.text( + 0.62, + 0.95, + rf"({float(lats[index]):.1f}$\degree$, " + rf"{float(lons[index]):.1f}$\degree$)", + transform=ax.transAxes, + ) + ax.set_ylim([-0.2, 0.8]) + ax.set_xlabel("Global thermal expansion (m)") + ax.set_ylabel("Dynamic sea level (m)") + ax.legend(loc="upper left", frameon=False) + return fig + + +def plot_evals(diff_list: list, zos: list, mse_list: list) -> plt.figure: + """Plot the evaluation of the thermosteric patterns. + + Parameters + ---------- + diff_list: list + list of difference maps + zos: list + list of zos scenarios + mse_list: list + list of mean squared errors + + Returns + ------- + fig: plt.figure + figure of the evaluation + """ + # Plot the mse for each scenario + fig = plt.figure(figsize=(12, 5), layout="constrained") + vmin = -0.5 + vmax = 0.5 + titles = ["SSP2-4.5", "SSP3-7.0", "SSP5-8.5"] + for index in range(3): + ax = fig.add_subplot(2, 3, index + 1, projection=ccrs.PlateCarree()) + ax.pcolormesh( + zos[index].coord("longitude").points, + zos[index].coord("latitude").points, + diff_list[index], + transform=ccrs.PlateCarree(), + vmin=vmin, + vmax=vmax, + cmap="RdBu_r", + ) + ax.set_title(titles[index]) + cbar = plt.colorbar(ax.collections[0], ax=ax, orientation="horizontal") + cbar.set_label("Prediction - ESM (m)") + + ax = fig.add_subplot(2, 3, (4, 6)) + time = np.linspace(2015, 2100, 1032) + ax.plot(time, mse_list[0][: (86 * 12)], label="SSP245", color="navy") + ax.plot(time, mse_list[1][: (86 * 12)], label="SSP370", color="orange") + ax.plot(time, mse_list[2][: (86 * 12)], label="SSP585", color="darkorchid") + ax.set_xlabel("Year") + ax.set_ylabel("Global mean squared error (m)") + ax.legend(loc="upper left", frameon=False) + return fig + + +def evaluate_patterns( + zostoga_list: list, + zos_list: list, + slopes: list, + plot_path: Path, + model: str, +) -> None: + """Evaluate the patterns. + + Parameters + ---------- + zostoga_list: list + list of zostoga scenarios + zos_list: list + list of zos scenarios + slopes: list + list of slopes + plot_path: Path + path to save plots to + model: str + model name + + Returns + ------- + None + """ + # Scale patterns for each scenario + mse_list = [] + diff_list = [] + for index, (zostoga, slope) in enumerate( + zip(zostoga_list, slopes, strict=True) + ): + zostoga.data = zostoga.data - np.mean(zostoga.data[0:10]) + zos_list[index].data = zos_list[index].data - np.mean( + zos_list[index].data[0:10], axis=0 + ) + + p_scaled = ( + zostoga.data[:, np.newaxis, np.newaxis] * slope[np.newaxis, :, :] + ) + + # Diff maps for end of century (20 yr mean) + end_idx = (86 * 12) - 1 # 86 years x 12 months + start_idx = end_idx - (20 * 12) # 20 years x 12 months + diff_list.append( + np.mean( + ( + zos_list[index][start_idx:end_idx].data + + zostoga.data[start_idx:end_idx, np.newaxis, np.newaxis] + ), + axis=0, + ) + - np.mean(p_scaled[start_idx:end_idx], axis=0) + ) + + # Calculate mean squared error + mse = np.nanmean( + ( + ( + zos_list[index].data + + zostoga.data[:, np.newaxis, np.newaxis] + ) + - p_scaled + ) + ** 2, + axis=(1, 2), + ) + mse_list.append(mse) + + fig = plot_evals(diff_list, zos_list, mse_list) + fig.savefig(Path(plot_path) / f"mse_{model}.png", dpi=150) + plt.close() + + +def extract_data_from_cfg(model: str, cfg: dict) -> tuple[list]: + """Extract model data from the cfg. + + Parameters + ---------- + model : str + model name + cfg: dict + dictionary passed in by ESMValTool preprocessors + + Returns + ------- + zostoga: list + list of zostoga scenarios + zos: list + list of zos scenarios + """ + zostoga_names = [ + "zostoga_piControl", + "zostoga_245", + "zostoga_370", + "zostoga_585", + ] + zos_names = ["zos_245", "zos_370", "zos_585"] + zostoga_cubes = [] + zos_cubes = [] + for dataset in cfg["input_data"].values(): + if dataset["dataset"] == model: + for zostoga_name in zostoga_names: + if dataset["variable_group"] == zostoga_name: + input_file = dataset["filename"] + zostoga_cubes.append(sf.load_cube(input_file)) + + for zos_name in zos_names: + if dataset["variable_group"] == zos_name: + input_file = dataset["filename"] + zos_cubes.append(sf.load_cube(input_file)) + + return zostoga_cubes, zos_cubes + + +def prepare_zostoga(zostoga_list: list, plot_path: Path) -> list: + """Prepare the zostoga cube for regression. + + Parameters + ---------- + zostoga_list: list + list of raw zostoga cubes + plot_path: Path + path to plot folder + + Returns + ------- + zostoga_list: list + list of zostoga cubes + """ + # Calculate drift from PiControl zostoga + zostoga_drift = calculate_drift(zostoga_list[0]) + + # Detrend the scenario zostogas + return [ + detrend_zostoga(zostoga, zostoga_drift, plot_path) + for zostoga in zostoga_list[1:] + ] # [1:] to skip PiControl + + +def patterns(model: str, cfg: dict) -> None: + """Run pattern calculations. + + Parameters + ---------- + model : str + mame of the model to calculate patterns for + cfg : dict + global config dictionary + + Returns + ------- + None + """ + work_path, plot_path = sf.make_model_dirs(cfg, model) + zostoga_list, zos_list = extract_data_from_cfg(model, cfg) + + # Prepare zostoga for regression + zostoga_list = prepare_zostoga(zostoga_list, plot_path) + + # Calculate regression between zostoga and zos + scenarios = ["ssp245", "ssp370", "ssp585"] + slopes, masks = [], [] + for index, (z_dtr, zos) in enumerate( + zip(zostoga_list, zos_list, strict=True) + ): + slopes_arr, masks_arr = dyn_steric_regression( + z_dtr, zos, plot_path, scenarios[index] + ) + save_data(slopes_arr, masks_arr, work_path, model, scenarios[index]) + slopes.append(slopes_arr) + masks.append(masks_arr) + + # Test the patterns + evaluate_patterns(zostoga_list, zos_list, slopes, plot_path, model) + + +def main(cfg: dict) -> None: + """Take in driving data with parallelisation options. + + Parameters + ---------- + cfg : dict + the global config dictionary, passed by ESMValTool. + + Returns + ------- + None + """ + input_data = cfg["input_data"].values() + + models = [] + for mod in input_data: + model = mod["dataset"] + if model not in models: + models.append(model) + + sf.parallelise(patterns, processes=8)(models, cfg) + + # Log provenance + model_work_dir = Path(cfg["work_dir"]) + provenance_record = get_provenance_record() + path = Path(model_work_dir / "patterns.nc") + with ProvenanceLogger(cfg) as provenance_logger: + provenance_logger.log(path, provenance_record) + + +if __name__ == "__main__": + with run_diagnostic() as config: + main(config) diff --git a/esmvaltool/diag_scripts/steric_patterns/sub_funcs.py b/esmvaltool/diag_scripts/steric_patterns/sub_funcs.py new file mode 100644 index 0000000000..d59b160d14 --- /dev/null +++ b/esmvaltool/diag_scripts/steric_patterns/sub_funcs.py @@ -0,0 +1,98 @@ +# (C) Crown Copyright 2022-2025, Met Office. +"""Script containing sub-functions for main scripts. + +Author +------ +Gregory Munday (Met Office, UK) +""" + +from __future__ import annotations + +import logging +import multiprocessing as mp +from functools import partial +from pathlib import Path + +import iris +import iris.analysis.cartography +import iris.coord_categorisation + +logger = logging.getLogger(Path(__file__).stem) + + +def load_cube(filename: str) -> iris.cube.Cube: + """Load cube, remove any dimensions of length: 1. + + Parameters + ---------- + filename : path + path to load cube file + + Returns + ------- + cube : cube + a cube + """ + logger.debug("Loading %s", filename) + cube = iris.load_cube(filename) + return iris.util.squeeze(cube) + + +def make_model_dirs(cfg: dict, model: str) -> tuple[Path, Path]: + """Create directories for each input model for saving. + + Parameters + ---------- + cfg: dict + Dictionary passed in by ESMValTool preprocessors + model : str + model name + + Returns + ------- + model_work_dir : path + path to specific model directory in work_dir + model_plot_dir : path + path to specific plot directory in plot_dir + """ + work_path = cfg["work_dir"] + plot_path = cfg["plot_dir"] + model_work_dir = Path(work_path) / model + model_plot_dir = Path(plot_path) / model + + if not Path.exists(model_work_dir): + Path.mkdir(model_work_dir) + if not Path.exists(model_plot_dir): + Path.mkdir(model_plot_dir) + return model_work_dir, model_plot_dir + + +def parallelise(function: callable, processes: int | None = None) -> callable: + """Parallelise any function, by George Ford, Met Office. + + Parameters + ---------- + function : function + function to be parallelised + processes : int + number of threads to be used in parallelisation + + Returns + ------- + result : any + results of parallelised elements + """ + if processes is None: + processes = max(1, mp.cpu_count() - 1) + if processes <= 0: + processes = 1 + + def easy_parallise(func: callable, sequence: list, cfg: dict) -> list: + with mp.Pool(processes=processes) as pool: + config_wrapper = partial(func, cfg=cfg) + result = pool.map_async(config_wrapper, sequence).get() + pool.close() + pool.join() + return result + + return partial(easy_parallise, function) diff --git a/esmvaltool/recipes/recipe_steric_patterns.yml b/esmvaltool/recipes/recipe_steric_patterns.yml new file mode 100644 index 0000000000..79969a2196 --- /dev/null +++ b/esmvaltool/recipes/recipe_steric_patterns.yml @@ -0,0 +1,154 @@ +# ESMValTool +# recipe_steric_patterns.yml +--- +documentation: + description: Generating sterodynamic sea-level patterns from CMIP6 models. + title: Sterodynamic sea-level patterns + + authors: + - munday_gregory + + maintainer: + - munday_gregory + + references: + - palmer2020 + - perks2023 + +preprocessors: + regular_grid: + regrid: + target_grid: {start_longitude: -179.5, end_longitude: 179.5, step_longitude: 1, + start_latitude: -89.5, end_latitude: 89.5, step_latitude: 1} + scheme: linear + +CMIP_settings: &CMIP_settings + mip: Omon + project: CMIP6 + activity: CMIP + +ScenarioMIP_settings: &ScenarioMIP_settings + mip: Omon + project: CMIP6 + activity: ScenarioMIP + +ScenarioMIP_settings_zos: &ScenarioMIP_settings_zos + mip: Omon + project: CMIP6 + activity: ScenarioMIP + preprocessor: regular_grid + +CMIP6_zostoga: &cmip6_zostoga + - {dataset: ACCESS-CM2, ensemble: r1i1p1f1, grid: gn, institute: CSIRO-ARCCSS} + - {dataset: ACCESS-ESM1-5, ensemble: r1i1p1f1, grid: gn} + # - {dataset: BCC-CSM2-MR, exp: [piControl, ssp245, ssp370, ssp585], ensemble: r1i1p1f1, grid: gn} + - {dataset: CanESM5, ensemble: r1i1p1f1, grid: gn} + - {dataset: CanESM5-CanOE, ensemble: r1i1p2f1, grid: gn} + - {dataset: CanESM5-1, ensemble: r1i1p1f1, grid: gn, institute: CCCma} + - {dataset: CMCC-CM2-SR5, ensemble: r1i1p1f1, grid: gn} + - {dataset: CNRM-CM6-1, ensemble: r1i1p1f2, grid: gn} + - {dataset: CNRM-CM6-1-HR, ensemble: r1i1p1f2, grid: gn} + - {dataset: CNRM-ESM2-1, ensemble: r1i1p1f2, grid: gn} + - {dataset: EC-Earth3, ensemble: r1i1p1f1, grid: gn} + # - {dataset: EC-Earth3-CC, ensemble: r1i1p1f1, grid: gn} + # - {dataset: EC-Earth3-Veg, ensemble: r1i1p1f1, grid: gn} + # - {dataset: GFDL-CM4, ensemble: r1i1p1f1, grid: gr} + # - {dataset: GFDL-ESM4, ensemble: r1i1p1f1, grid: gr} + - {dataset: GISS-E2-1-H, ensemble: r1i1p1f2, grid: gn} + - {dataset: GISS-E2-1-G, ensemble: r1i1p5f1, grid: gn} + - {dataset: GISS-E2-2-G, ensemble: r1i1p3f1, grid: gn} + # - {dataset: HadGEM3-GC31-LL, ensemble: r1i1p1f1, grid: gm} + # - {dataset: HadGEM3-GC31-MM, ensemble: r1i1p1f1, grid: gm} + - {dataset: INM-CM4-8, ensemble: r1i1p1f1, grid: gr1} + - {dataset: INM-CM5-0, ensemble: r1i1p1f1, grid: gr1} + # - {dataset: IPSL-CM6A-LR, ensemble: r1i1p1f1, grid: gn} + - {dataset: MIROC6, ensemble: r1i1p1f1, grid: gm} + - {dataset: MPI-ESM1-2-HR, ensemble: r1i1p1f1, grid: gn} + - {dataset: MPI-ESM1-2-LR, ensemble: r1i1p1f1, grid: gn} + - {dataset: MRI-ESM2-0, ensemble: r1i1p1f1, grid: gm} + - {dataset: NorESM2-LM, ensemble: r1i1p1f1, grid: gm} + - {dataset: NorESM2-MM, ensemble: r1i1p1f1, grid: gm} + - {dataset: UKESM1-0-LL, ensemble: r1i1p1f2, grid: gm} + +CMIP6_zos: &cmip6_zos + - {dataset: ACCESS-CM2, ensemble: r1i1p1f1, grid: gn, institute: CSIRO-ARCCSS} + - {dataset: ACCESS-ESM1-5, ensemble: r1i1p1f1, grid: gn} + # - {dataset: BCC-CSM2-MR, ensemble: r1i1p1f1, grid: gn} + - {dataset: CanESM5, ensemble: r1i1p1f1, grid: gn} + - {dataset: CanESM5-CanOE, ensemble: r1i1p2f1, grid: gn} + - {dataset: CanESM5-1, ensemble: r1i1p1f1, grid: gn, institute: CCCma} + - {dataset: CMCC-CM2-SR5, ensemble: r1i1p1f1, grid: gn} + - {dataset: CNRM-CM6-1, ensemble: r1i1p1f2, grid: gn} + - {dataset: CNRM-CM6-1-HR, ensemble: r1i1p1f2, grid: gn} + - {dataset: CNRM-ESM2-1, ensemble: r1i1p1f2, grid: gn} + - {dataset: EC-Earth3, ensemble: r1i1p1f1, grid: gn} + # - {dataset: EC-Earth3-CC, ensemble: r1i1p1f1, grid: gn} + # - {dataset: EC-Earth3-Veg, ensemble: r1i1p1f1, grid: gn} + # - {dataset: GFDL-CM4, ensemble: r1i1p1f1, grid: gr} + # - {dataset: GFDL-ESM4, ensemble: r1i1p1f1, grid: gr} + - {dataset: GISS-E2-1-H, ensemble: r1i1p1f2, grid: gr} + - {dataset: GISS-E2-1-G, ensemble: r1i1p5f1, grid: gn} + - {dataset: GISS-E2-2-G, ensemble: r1i1p3f1, grid: gn} + # - {dataset: HadGEM3-GC31-LL, ensemble: r1i1p1f1, grid: gn} # Missing ssp370 + # - {dataset: HadGEM3-GC31-MM, ensemble: r1i1p1f1, grid: gn} # Missing ssp370 + - {dataset: INM-CM4-8, ensemble: r1i1p1f1, grid: gr1} + - {dataset: INM-CM5-0, ensemble: r1i1p1f1, grid: gr1} + # - {dataset: IPSL-CM6A-LR, ensemble: r1i1p1f1, grid: gn} + - {dataset: MIROC6, ensemble: r1i1p1f1, grid: gn} + - {dataset: MPI-ESM1-2-HR, ensemble: r1i1p1f1, grid: gn} + - {dataset: MPI-ESM1-2-LR, ensemble: r1i1p1f1, grid: gn} + - {dataset: MRI-ESM2-0, ensemble: r1i1p1f1, grid: gr} + - {dataset: NorESM2-LM, ensemble: r1i1p1f1, grid: gn} + - {dataset: NorESM2-MM, ensemble: r1i1p1f1, grid: gn} + - {dataset: UKESM1-0-LL, ensemble: r1i1p1f2, grid: gn} + +diagnostics: + steric_patterns: + description: Monthly steric sea-level patterns. + + variables: + zostoga_piControl: + short_name: zostoga + exp: piControl + <<: *CMIP_settings + additional_datasets: *cmip6_zostoga + + zostoga_245: + short_name: zostoga + exp: ssp245 + <<: *ScenarioMIP_settings + additional_datasets: *cmip6_zostoga + + zostoga_370: + short_name: zostoga + exp: ssp370 + <<: *ScenarioMIP_settings + additional_datasets: *cmip6_zostoga + + zostoga_585: + short_name: zostoga + exp: ssp585 + <<: *ScenarioMIP_settings + additional_datasets: *cmip6_zostoga + + zos_245: + short_name: zos + exp: ssp245 + <<: *ScenarioMIP_settings_zos + additional_datasets: *cmip6_zos + + zos_370: + short_name: zos + exp: ssp370 + <<: *ScenarioMIP_settings_zos + additional_datasets: *cmip6_zos + + zos_585: + short_name: zos + exp: ssp585 + <<: *ScenarioMIP_settings_zos + additional_datasets: *cmip6_zos + + scripts: + climate_patterns_script: + script: steric_patterns/steric_patterns.py diff --git a/esmvaltool/references/palmer2020.bibtex b/esmvaltool/references/palmer2020.bibtex new file mode 100644 index 0000000000..de63118200 --- /dev/null +++ b/esmvaltool/references/palmer2020.bibtex @@ -0,0 +1,15 @@ +@article{https://doi.org/10.1029/2019EF001413, +author = {Palmer, M. D. and Gregory, J. M. and Bagge, M. and Calvert, D. and Hagedoorn, J. M. and Howard, T. and Klemann, V. and Lowe, J. A. and Roberts, C. D. and Slangen, A. B. A. and Spada, G.}, +title = {Exploring the Drivers of Global and Local Sea-Level Change Over the 21st Century and Beyond}, +journal = {Earth's Future}, +volume = {8}, +number = {9}, +pages = {e2019EF001413}, +keywords = {climate change, CMIP5 models, RCP scenarios, sea-level projections, tide gauge observations}, +doi = {https://doi.org/10.1029/2019EF001413}, +url = {https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/2019EF001413}, +eprint = {https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/2019EF001413}, +note = {e2019EF001413 2019EF001413}, +abstract = {Abstract We present a new set of global and local sea-level projections at example tide gauge locations under the RCP2.6, RCP4.5, and RCP8.5 emissions scenarios. Compared to the CMIP5-based sea-level projections presented in IPCC AR5, we introduce a number of methodological innovations, including (i) more comprehensive treatment of uncertainties, (ii) direct traceability between global and local projections, and (iii) exploratory extended projections to 2300 based on emulation of individual CMIP5 models. Combining the projections with observed tide gauge records, we explore the contribution to total variance that arises from sea-level variability, different emissions scenarios, and model uncertainty. For the period out to 2300 we further breakdown the model uncertainty by sea-level component and consider the dependence on geographic location, time horizon, and emissions scenario. Our analysis highlights the importance of local variability for sea-level change in the coming decades and the potential value of annual-to-decadal predictions of local sea-level change. Projections to 2300 show a substantial degree of committed sea-level rise under all emissions scenarios considered and highlight the reduced future risk associated with RCP2.6 and RCP4.5 compared to RCP8.5. Tide gauge locations can show large ( > 50\%) departures from the global average, in some cases even reversing the sign of the change. While uncertainty in projections of the future Antarctic ice dynamic response tends to dominate post-2100, we see substantial differences in the breakdown of model variance as a function of location, time scale, and emissions scenario.}, +year = {2020} +} diff --git a/esmvaltool/references/perks2023.bibtex b/esmvaltool/references/perks2023.bibtex new file mode 100644 index 0000000000..3524ede775 --- /dev/null +++ b/esmvaltool/references/perks2023.bibtex @@ -0,0 +1,8 @@ +@article{perks2023, + author = {Perks, R. and Weeks, J.}, + doi = {10.5281/zenodo.10255468}, + title = {Met Office Projecting Future Sea Level (ProFSea) tool}, + url = {https://github.com/MetOffice/ProFSea-tool/}, + journal = {Zenodo}, + year = {2023}, +}