From e242a9ce082bae6f170b2b5b568b4451f1ef7e35 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 10:25:34 +0100 Subject: [PATCH 01/70] Added recipe file, climate patterns only, no EBM parameter script --- .../recipes/recipe_climate_patterns_only | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 esmvaltool/recipes/recipe_climate_patterns_only diff --git a/esmvaltool/recipes/recipe_climate_patterns_only b/esmvaltool/recipes/recipe_climate_patterns_only new file mode 100644 index 0000000000..cbca21a02f --- /dev/null +++ b/esmvaltool/recipes/recipe_climate_patterns_only @@ -0,0 +1,180 @@ +# ESMValTool +# recipe_climate_patterns.yml +--- +documentation: + description: Generating climate patterns from CMIP6 models. + title: Generating Climate Patterns + + authors: + - munday_gregory + +preprocessors: + global_mean_monthly: + monthly_statistics: + operator: mean + + regrid: + target_grid: {start_longitude: -180, end_longitude: 176.25, step_longitude: 3.75, + start_latitude: -55, end_latitude: 82.5, step_latitude: 2.5} + scheme: linear + +monthly_global_settings: &monthly_global_settings + mip: Amon + project: CMIP6 + preprocessor: global_mean_monthly + +monthly_global_settings_day: &monthly_global_settings_day + mip: day + project: CMIP6 + preprocessor: global_mean_monthly + +CMIP6_DAY: &cmip6_no_tasmax + - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r2i1p1f1, grid: gr, start_year: 1850, end_year: 2099} + - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: NorESM2-MM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: TaiESM1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + +CMIP6_DAY: &cmip6_day + - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r2i1p1f1, grid: gr, start_year: 1850, end_year: 2099} + - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: NorESM2-MM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: TaiESM1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + +CMIP6_FULL: &cmip6_full + - {dataset: ACCESS-CM2, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100, institute: CSIRO-ARCCSS} + - {dataset: ACCESS-ESM1-5, exp: [historical, ssp585], ensemble: r3i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: AWI-CM-1-1-MR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: BCC-CSM2-MR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: CanESM5, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: CanESM5-CanOE, exp: [historical, ssp585], ensemble: r1i1p2f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: CAS-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: CMCC-ESM2, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: CNRM-CM6-1, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} + - {dataset: CNRM-CM6-1-HR, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} + - {dataset: CNRM-ESM2-1, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} + - {dataset: EC-Earth3, exp: [historical, ssp585], ensemble: r11i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + - {dataset: EC-Earth3-CC, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + - {dataset: EC-Earth3-Veg, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + - {dataset: FGOALS-g3, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: FIO-ESM-2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: GFDL-CM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} + - {dataset: GFDL-ESM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} + - {dataset: GISS-E2-1-H, exp: [historical, ssp585], ensemble: r3i1p1f2, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: GISS-E2-1-G, exp: [historical, ssp585], ensemble: r1i1p5f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: GISS-E2-2-G, exp: [historical, ssp585], ensemble: r1i1p3f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: HadGEM3-GC31-LL, exp: [historical, ssp585], ensemble: r1i1p1f3, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: HadGEM3-GC31-MM, exp: [historical, ssp585], ensemble: r1i1p1f3, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: INM-CM4-8, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} + - {dataset: INM-CM5-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} + - {dataset: IPSL-CM6A-LR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + - {dataset: KACE-1-0-G, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + - {dataset: MIROC6, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: MIROC-ES2L, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: MPI-ESM1-2-HR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: MPI-ESM1-2-LR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: MRI-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: UKESM1-0-LL, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gn, start_year: 1850, end_year: 2100} + +diagnostics: + monthly_timeseries: + description: Mean monthly variables + + variables: + tasmax_585: + short_name: tasmax + <<: *monthly_global_settings + additional_datasets: *cmip6_full + + tasmin_585: + short_name: tasmin + <<: *monthly_global_settings + additional_datasets: *cmip6_full + + tas_585: + short_name: tas + <<: *monthly_global_settings + additional_datasets: *cmip6_full + + huss_585: + short_name: huss + <<: *monthly_global_settings + additional_datasets: *cmip6_full + + pr_585: + short_name: pr + <<: *monthly_global_settings + additional_datasets: *cmip6_full + + sfcWind_585: + short_name: sfcWind + <<: *monthly_global_settings + additional_datasets: *cmip6_full + + ps_585: + short_name: ps + <<: *monthly_global_settings + additional_datasets: *cmip6_full + + rsds_585: + short_name: rsds + <<: *monthly_global_settings + additional_datasets: *cmip6_full + + rlds_585: + short_name: rlds + <<: *monthly_global_settings + additional_datasets: *cmip6_full + + tasmax_585_day: + short_name: tasmax + <<: *monthly_global_settings_day + additional_datasets: *cmip6_day + + tasmin_585_day: + short_name: tasmin + <<: *monthly_global_settings_day + additional_datasets: *cmip6_day + + tas_585_no_tasmax: + short_name: tas + <<: *monthly_global_settings + additional_datasets: *cmip6_no_tasmax + + huss_585_no_tasmax: + short_name: huss + <<: *monthly_global_settings + additional_datasets: *cmip6_no_tasmax + + pr_585_no_tasmax: + short_name: pr + <<: *monthly_global_settings + additional_datasets: *cmip6_no_tasmax + + sfcWind_585_no_tasmax: + short_name: sfcWind + <<: *monthly_global_settings + additional_datasets: *cmip6_no_tasmax + + ps_585_no_tasmax: + short_name: ps + <<: *monthly_global_settings + additional_datasets: *cmip6_no_tasmax + + rsds_585: + short_name: rsds + <<: *monthly_global_settings + additional_datasets: *cmip6_no_tasmax + + rlds_585: + short_name: rlds + <<: *monthly_global_settings + additional_datasets: *cmip6_no_tasmax + + scripts: + climate_patterns_script: + script: climate_patterns/climate_patterns.py + grid: constrained # options: constrained, full + imogen_mode: on # options: on, off + output_r2_scores: on # options: on, off + parallelise: on # options: on, off + parallel_threads: 40 # int, optional From fa45e83342d09f66b2e1f3e8ee901c970de1a630 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 11:07:45 +0100 Subject: [PATCH 02/70] adding rest of files --- doc/sphinx/source/recipes/index.rst | 1 + .../recipes/recipe_climate_patterns.rst | 115 ++++ .../climate_patterns_only/climate_patterns.py | 616 ++++++++++++++++++ .../climate_patterns_only/cp_plotting.py | 186 ++++++ .../climate_patterns_only/rename_variables.py | 222 +++++++ .../climate_patterns_only/sub_functions.py | 276 ++++++++ 6 files changed, 1416 insertions(+) create mode 100644 doc/sphinx/source/recipes/recipe_climate_patterns.rst create mode 100644 esmvaltool/diag_scripts/climate_patterns_only/climate_patterns.py create mode 100644 esmvaltool/diag_scripts/climate_patterns_only/cp_plotting.py create mode 100644 esmvaltool/diag_scripts/climate_patterns_only/rename_variables.py create mode 100644 esmvaltool/diag_scripts/climate_patterns_only/sub_functions.py diff --git a/doc/sphinx/source/recipes/index.rst b/doc/sphinx/source/recipes/index.rst index 0ad90d42e4..db63396397 100644 --- a/doc/sphinx/source/recipes/index.rst +++ b/doc/sphinx/source/recipes/index.rst @@ -12,6 +12,7 @@ Atmosphere :maxdepth: 1 recipe_miles + recipe_climate_patterns recipe_clouds recipe_cmug_h2o recipe_crem diff --git a/doc/sphinx/source/recipes/recipe_climate_patterns.rst b/doc/sphinx/source/recipes/recipe_climate_patterns.rst new file mode 100644 index 0000000000..2441f3069d --- /dev/null +++ b/doc/sphinx/source/recipes/recipe_climate_patterns.rst @@ -0,0 +1,115 @@ +.. _recipes_climate_patterns: + +Generating Climate Patterns from CMIP6 Models +============================================= + +Overview +-------- + +The recipe recipe_climate_patterns generates climate patterns from CMIP6 model +datasets. + +.. note:: + The regrid setting in the recipe is set to a 2.5x3.75 grid. This is done to + match the current resolution in the IMOGEN-JULES framework, but can be + adjusted with no issues for a finer/coarser patterns grid. + + +Available recipes and diagnostics +--------------------------------- + +Recipes are stored in esmvaltool/recipes/ + + * recipe_climate_patterns.yml + +Diagnostics are stored in esmvaltool/diag_scripts/climate_patterns/ + + * climate_patterns.py: generates climate patterns from input datasets + * rename_variables.py: renames variables depending on user specifications + * sub_functions.py: set of sub functions to assist with driving scripts + * plotting.py: contains all plotting functions for driving scripts + + +User settings in recipe +----------------------- + +#. Script climate_patterns.py + + *Required settings for script* + + None + + *Optional settings for script* + + * grid: whether you want to remove Antarctic latitudes or not + * imogen_mode: output imogen-specific var names + .nc files + * output_r2_scores: output measures of pattern robustness (adds runtime) + * parallelise: parallelise over models or not + * parallel_threads: if you want to paralellise, how many threads you want + + *Required settings for variables* + + * short_name + * additional_datasets + + *Optional settings for variables* + + None + + *Required settings for preprocessor* + + * monthly_statistics: converts data to mean monthly data + + *Optional settings for preprocessor* + + * regrid: regrids data + + +Variables +--------- + +#. Script climate_patterns.py + +* tasmax (atmos, monthly, longitude latitude time) +* tasmin (atmos, monthly, longitude latitude time) +* tas (atmos, monthly, longitude latitude time) +* hurs (atmos, monthly, longitude latitude time) +* huss (atmos, monthly, longitude latitude time) +* pr (atmos, monthly, longitude latitude time) +* sfcWind (atmos, monthly, longitude latitude time) +* ps (atmos, monthly, longitude latitude time) +* rsds (atmos, monthly, longitude latitude time) +* rlds (atmos, monthly, longitude latitude time) + + +Observations and reformat scripts +--------------------------------- + +None + +References +---------- + +* Huntingford, C., Cox, P. An analogue model to derive additional climate + change scenarios from existing GCM simulations. + Climate Dynamics 16, 575–586 (2000). https://doi.org/10.1007/s003820000067 + +Example plots +------------- + +.. _fig_climate_patterns_2: +.. figure:: /recipes/figures/climate_patterns/patterns.png + :align: center + :width: 80% + + Patterns generated for CMIP6 models, gridded view. Patterns are shown per + variable, for the month of January. + +.. _fig_climate_patterns_3: +.. figure:: /recipes/figures/climate_patterns/score_timeseries.png + :align: center + :width: 80% + + R2 scores of patterns fitting per variable. Diversity of scores sits in the + literatures' range: with temperature, specific humidity and longwave + downwelling radiation being the most robust fits. \ No newline at end of file diff --git a/esmvaltool/diag_scripts/climate_patterns_only/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns_only/climate_patterns.py new file mode 100644 index 0000000000..704b180642 --- /dev/null +++ b/esmvaltool/diag_scripts/climate_patterns_only/climate_patterns.py @@ -0,0 +1,616 @@ +"""Diagnostic script to build climate patterns from CMIP6 models. + +Description +----------- +Builds patterns, anomaly and climatology cubes from CMIP6 models. +This diagnostic needs preprocessed mean monthly cubes, with no +gridding requirements. Default re-grid specification exists to +decrease CPU-load and run-time. + +Author +------ +Gregory Munday (Met Office, UK) + + +Configuration options in recipe +------------------------------- +grid: str, optional (default: constrained) + options: constrained, full + def: removes Antarctica from grid +imogen_mode: bool, optional (default: off) + options: on, off + def: outputs extra data (anomaly, climatology) per variable + to drive JULES-IMOGEN configuration +output_r2_scores: bool, optional (default: off) + options: on, off + def: outputs determinant values per variable to measure pattern robustness +parallelise: bool, optional (default: off) + options: on, off + def: parallelises code to run N models at once +parallel_threads: int, optional (default: null) + options: any int, up to the amount of CPU cores accessible by user + def: number of threads/cores to parallelise over. If 'parallelise: on' and + 'parallel_threads': null, diagnostic will automatically parallelise + over N-1 accessible threads +""" + +import logging +from pathlib import Path + +import iris +import iris.coord_categorisation +import iris.cube +import numpy as np +import sklearn.linear_model +import sub_functions as sf +from cp_plotting import ( + plot_cp_timeseries, + plot_patterns, + plot_scores, +) +from rename_variables import ( + rename_anom_variables, + rename_clim_variables, + rename_regression_variables, + rename_variables_base, +) + +from esmvaltool.diag_scripts.shared import ProvenanceLogger, run_diagnostic + +logger = logging.getLogger(Path(__file__).stem) + + +def climatology(cube, syr=1850, eyr=1889): + """Handles aggregation to make climatology. + + Parameters + ---------- + cube : cube + cube loaded from config dictionary + + Returns + ------- + cube_aggregated (cube): 40 year climatology cube from syr-eyr + (default 1850-1889) + """ + cube_40yr = cube.extract( + iris.Constraint(time=lambda t: syr <= t.point.year <= eyr, + month_number=lambda t: 1 <= t.point <= 12)) + cube_aggregated = make_monthly_climatology(cube_40yr) + + return cube_aggregated + + +def constrain_latitude(cube, min_lat=-55, max_lat=82.5): + """Constrains latitude to decrease run-time when output fed to IMOGEN. + + Parameters + ---------- + cube (cube): cube loaded from config dictionary + min_lat (float): minimum latitude to crop + max_lat (float): maximum latitude to crop + + Returns + ------- + cube_clipped (cube): cube with latitudes set from -55 to 82.5 degrees + """ + cube_clipped = cube.extract( + iris.Constraint(latitude=lambda cell: max_lat >= cell >= min_lat)) + + return cube_clipped + + +def make_monthly_climatology(cube): + """Generate a climatology by month_number. + + Parameters + ---------- + cube (cube): cube loaded from config dictionary + + Returns + ------- + cube_month_climatol (cube): cube aggregated by month_number + """ + if not cube.coords("month_number"): + iris.coord_categorisation.add_month_number(cube, "time", + "month_number") + cube_month_climatol = cube.aggregated_by("month_number", + iris.analysis.MEAN) + + return cube_month_climatol + + +def diurnal_temp_range(cubelist): + """Calculate diurnal range from monthly max and min temperatures. + + Parameters + ---------- + cubelist (cubelist): cubelist of tasmin and tasmax + + Returns + ------- + range_cube (cube): cube of calculated diurnal range + """ + range_cube = cubelist[0] - cubelist[1] + + # check in case cubes are wrong way around + if np.mean(range_cube.data) < 0: + range_cube = -range_cube + + range_cube.rename("Diurnal Range") + range_cube.var_name = ("range_tl1") + + return range_cube + + +def calculate_diurnal_range(clim_list, ts_list): + """Facilitate diurnal range calculation and appending. + + Parameters + ---------- + clim_list (cubelist): cubelist of climatology cubes + ts_list (cubelist): cubelist of standard timeseries cubes + + Returns + ------- + clim_list_final (cubelist): cubelist of climatology cubes + including diurnal range + ts_list_final (cubelist): cubelist of standard timeseries cubes + including diurnal range + """ + temp_range_list_clim = iris.cube.CubeList([]) + temp_range_list_ts = iris.cube.CubeList([]) + comb_list = [clim_list, ts_list] + + for i, _ in enumerate(comb_list): + for cube in comb_list[i]: + if (cube.var_name in ('tasmax', 'tasmin')) \ + and cube in clim_list: + temp_range_list_clim.append(cube) + elif (cube.var_name in ('tasmax', 'tasmin')) \ + and cube in ts_list: + temp_range_list_ts.append(cube) + else: + pass + + derived_diurnal_clim = diurnal_temp_range(temp_range_list_clim) + derived_diurnal_ts = diurnal_temp_range(temp_range_list_ts) + + # append diurnal range to lists + clim_list_final, ts_list_final = append_diurnal_range( + derived_diurnal_clim, derived_diurnal_ts, clim_list, ts_list) + + return clim_list_final, ts_list_final + + +def append_diurnal_range(derived_diurnal_clim, derived_diurnal_ts, clim_list, + ts_list): + """Append diurnal range to cubelists. + + Parameters + ---------- + derived_diurnal_clim (cube): derived diurnal climatology cube + derived_diurnal_ts (cube): derived diurnal timeseries cube + clim_list (cubelist): existing climatology cubelist, no range + ts_list (cubelist): existing timeseries cubelist, no range + + Returns + ------- + clim_list_final (cubelist): cubelist of climatology cubes + including diurnal range + ts_list_final (cubelist): cubelist of standard timeseries cubes + including diurnal range + """ + # creating cube list without tasmax or tasmin + # (since we just wanted the diurnal range) + clim_list_final = iris.cube.CubeList([]) + ts_list_final = iris.cube.CubeList([]) + + for cube in clim_list: + if cube.var_name not in ('tasmax', 'tasmin'): + clim_list_final.append(cube) + + for cube in ts_list: + if cube.var_name not in ('tasmax', 'tasmin'): + ts_list_final.append(cube) + + clim_list_final.append(derived_diurnal_clim) + ts_list_final.append(derived_diurnal_ts) + + return clim_list_final, ts_list_final + + +def calculate_anomaly(clim_list, ts_list): + """Calculate variables as anomalies, and adds diurnal range as variable. + + Parameters + ---------- + clim_list (cubelist): cubelist of climatology variables + ts_list (cubelist): cubelist of standard variable timeseries + + Returns + ------- + clim_list_final (cubelist): cubelist of clim. vars, inc. diurnal range + anom_list_final (cubelist): cubelist of anomaly vars, inc. diurnal range + """ + # calculate diurnal temperature range cube + clim_list_final, ts_list_final = calculate_diurnal_range( + clim_list, ts_list) + + anom_list_final = ts_list_final.copy() + + # calc the anom by subtracting the monthly climatology from + # the time series + for i, _ in enumerate(ts_list_final): + i_months = anom_list_final[i].coord( + 'month_number').points - 1 # -1 because months are numbered 1..12 + anom_list_final[i].data -= clim_list_final[i][i_months].data + + return clim_list_final, anom_list_final + + +def regression(tas, cube_data): + """Calculate the coefficients of regression between global surface temp + and a variable. + + Parameters + ---------- + tas (cube): near-surface air temperature + cube_data (arr): cube.data array of a variable + + Returns + ------- + slope_array (arr): array of grid cells with same shape as initial cube, + containing the regression slope + score_array (arr): array of grid cells with same shape as initial cube, + containing the regression score as a measure of robustness + """ + slope_array = np.full(tas.data.shape[1:], np.nan) + score_array = np.full(tas.data.shape[1:], np.nan) + + # calculate global average + tas_data = sf.area_avg(tas, return_cube=False) + + for i in range(tas.data.shape[1]): + for j in range(tas.data.shape[2]): + if tas.data[0, i, j] is not np.ma.masked: + model = sklearn.linear_model.LinearRegression( + fit_intercept=False, copy_X=True) + + x = tas_data.reshape(-1, 1) + y = cube_data[:, i, j] + + model.fit(x, y) + slope_array[i, j] = model.coef_ + score_array[i, j] = model.score(x, y) + + return slope_array, score_array + + +def regression_units(tas, cube): + """Calculate regression coefficient units. + + Parameters + ---------- + tas (cube): near-surface air temperature + cube (cube): cube of a given variable + + Returns + ------- + units (str): string of calculated regression units + """ + print('Cube Units: ', cube.units) + units = cube.units / tas.units + print('Regression Units: ', units) + + return units + + +def calculate_regressions(anom_list, yrs=85): + """Facilitate the calculation of regression coefficients (climate + patterns) and the creation of a new cube of patterns per variable. + + Parameters + ---------- + anom_list (cubelist): cube list of variables as anomalies + + Returns + ------- + regr_var_list (cubelist): cube list of newly created regression slope + value cubes, for each variable + score_list (cubelist): cube list of newly created regression score cubes, + for each variable + """ + regr_var_list = iris.cube.CubeList([]) + score_list = iris.cube.CubeList([]) + months = yrs * 12 + + for cube in anom_list: + if cube.var_name == 'tl1_anom': + + # convert years to months when selecting + tas = cube[-months:] + + for cube in anom_list: + cube_ssp = cube[-months:] + month_list = iris.cube.CubeList([]) + score_month_list = iris.cube.CubeList([]) + + # extracting months, regressing, and merging + for i in range(1, 13): + month_constraint = iris.Constraint(imogen_drive=i) + month_cube_ssp = cube_ssp.extract(month_constraint) + month_tas = tas.extract(month_constraint) + + regr_array, score_array = regression(month_tas, + month_cube_ssp.data) + + # re-creating cube + if cube.var_name in ('swdown_anom', 'lwdown_anom'): + units = 'W m-2 K-1' + else: + units = regression_units(tas, cube_ssp) + + # assigning dim_coords + coord1 = tas.coord(contains_dimension=1) + coord2 = tas.coord(contains_dimension=2) + dim_coords_and_dims = [(coord1, 0), (coord2, 1)] + + # assigning aux_coord + coord_month = iris.coords.AuxCoord(i, var_name='imogen_drive') + aux_coords_and_dims = [(coord_month, ())] + + cube = rename_regression_variables(cube) + + # creating cube of regression values + regr_cube = iris.cube.Cube(regr_array, + units=units, + dim_coords_and_dims=dim_coords_and_dims, + aux_coords_and_dims=aux_coords_and_dims, + var_name=cube.var_name, + standard_name=cube.standard_name) + + # calculating cube of r2 scores + score_cube = iris.cube.Cube( + score_array, + units='R2', + dim_coords_and_dims=dim_coords_and_dims, + aux_coords_and_dims=aux_coords_and_dims, + var_name=cube.var_name, + standard_name=cube.standard_name) + + month_list.append(regr_cube) + score_month_list.append(score_cube) + + conc_cube = month_list.merge_cube() + regr_var_list.append(conc_cube) + + conc_score_cube = score_month_list.merge_cube() + score_list.append(conc_score_cube) + + return regr_var_list, score_list + + +def write_scores(scores, work_path): + """Save the global average regression scores per variable in a text file. + + Parameters + ---------- + scores (cubelist): cube list of regression score cubes, for each variable + work_path (path): path to work_dir, to save scores + + Returns + ------- + None + """ + for cube in scores: + score = sf.area_avg(cube, return_cube=False) + mean_score = np.mean(score) + + # saving scores + file = open(work_path + 'scores', 'a') + data = '{0:10.3f}'.format(mean_score) + name = cube.var_name + file.write(name + ': ' + data + '\n') + file.close() + + +def cube_saver(list_of_cubelists, work_path, name_list, mode): + """Save desired cubelists to work_dir, depending on switch settings. + + Parameters + ---------- + list_of_cubelists (list): list containing desired cubelists + work_path (path): path to work_dir, to save cubelists + name_list (list): list of filename strings for saving + mode (str): switch option passed through by ESMValTool config dict + + Returns + ------- + None + """ + if mode == 'imogen_scores': + for i in range(0, 4): + iris.save(list_of_cubelists[i], work_path + name_list[i]) + + if mode == 'imogen': + for i in range(0, 3): + iris.save(list_of_cubelists[i], work_path + name_list[i]) + + if mode == 'scores': + for i in range(2, 4): + for cube in list_of_cubelists[i]: + rename_variables_base(cube) + iris.save(list_of_cubelists[i], work_path + name_list[i]) + + if mode == 'base': + for cube in list_of_cubelists[2]: + rename_variables_base(cube) + iris.save(list_of_cubelists[2], work_path + name_list[2]) + + +def saving_outputs(clim_list_final, anom_list_final, regressions, scores, + imogen_mode, r2_scores, plot_path, work_path): + """Save data and plots to relevant directories. + + Parameters + ---------- + clim_list_final (cubelist): cube list of all variable climatologies + anom_list_final (cubelist): cube list of all variable anomalies + regressions (cubelist): cube list of all variable regression slopes + scores (cubelist): cube list of all variable regression scores + + Returns + ------- + None + """ + list_of_cubelists = [clim_list_final, anom_list_final, regressions, scores] + name_list = [ + "climatology_variables.nc", "anomaly_variables.nc", "patterns.nc", + "scores.nc" + ] + + # saving data + plotting + if imogen_mode is True: + if r2_scores is True: + plot_scores(list_of_cubelists[3], plot_path) + write_scores(scores, work_path) + plot_cp_timeseries(list_of_cubelists, plot_path) + cube_saver(list_of_cubelists, + work_path, + name_list, + mode='imogen_scores') + + else: + plot_cp_timeseries(list_of_cubelists, plot_path) + cube_saver(list_of_cubelists, work_path, name_list, mode='imogen') + + else: + if r2_scores is True: + plot_scores(list_of_cubelists[3], plot_path) + write_scores(scores, work_path) + plot_patterns(list_of_cubelists[2], plot_path) + cube_saver(list_of_cubelists, work_path, name_list, mode='scores') + + else: + plot_patterns(list_of_cubelists[2], plot_path) + cube_saver(list_of_cubelists, work_path, name_list, mode='base') + + +def get_provenance_record(): + """Create a provenance record describing the diagnostic data and plot. + + Parameters + ---------- + None + + Returns + ------- + record (dict): provenance record + """ + record = { + 'caption': ['Generating Climate Patterns from CMIP6 Models'], + 'statistics': ['mean', 'other'], + 'domains': ['global'], + 'themes': ['carbon'], + 'realms': ['atmos'], + 'authors': ['munday_gregory'], + } + + return record + + +def patterns(model): + """Driving function for script, taking in model data and saving parameters. + + Parameters + ---------- + model (str): model name + + Returns + ------- + None + """ + with run_diagnostic() as cfg: + input_data = cfg["input_data"].values() + grid_spec = cfg["grid"] + imogen_mode = cfg["imogen_mode"] + r2_scores = cfg["output_r2_scores"] + work_path = cfg["work_dir"] + "/" + plot_path = cfg["plot_dir"] + "/" + + clim_list = iris.cube.CubeList([]) + ts_list = iris.cube.CubeList([]) + + for dataset in input_data: + if dataset['dataset'] == model: + input_file = dataset["filename"] + + # preparing single cube + cube_initial = sf.compute_diagnostic(input_file) + + if grid_spec == "constrained": + cube = constrain_latitude(cube_initial) + else: + cube = cube_initial + + # appending to timeseries list + ts_list.append(cube) + + # making climatology + clim_cube = climatology(cube) + clim_list.append(clim_cube) + + # calculate anomaly over historical + ssp timeseries + clim_list_final, anom_list_final = calculate_anomaly(clim_list, ts_list) + + for i, _ in enumerate(clim_list_final): + rename_clim_variables(clim_list_final[i]) + rename_anom_variables(anom_list_final[i]) + + regressions, scores = calculate_regressions(anom_list_final.copy()) + + model_work_dir, model_plot_dir = sf.make_model_dirs( + cube_initial, work_path, plot_path) + + saving_outputs(clim_list_final, anom_list_final, regressions, scores, + imogen_mode, r2_scores, model_plot_dir, model_work_dir) + + provenance_record = get_provenance_record() + path = model_work_dir + 'patterns.nc' + with ProvenanceLogger(cfg) as provenance_logger: + provenance_logger.log(path, provenance_record) + + +def main(cfg): + """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() + parallelise = cfg["parallelise"] + threads = cfg["parallel_threads"] + + models = [] + for x in input_data: + model = x['dataset'] + if model not in models: + models.append(model) + + if parallelise is True: + sf.parallelise(patterns, threads)(models) + else: + for model in models: + patterns(model) + + +if __name__ == "__main__": + + with run_diagnostic() as config: + main(config) diff --git a/esmvaltool/diag_scripts/climate_patterns_only/cp_plotting.py b/esmvaltool/diag_scripts/climate_patterns_only/cp_plotting.py new file mode 100644 index 0000000000..411a1d1834 --- /dev/null +++ b/esmvaltool/diag_scripts/climate_patterns_only/cp_plotting.py @@ -0,0 +1,186 @@ +"""Script containing plotting functions for driving scripts. + +Author +------ +Gregory Munday (Met Office, UK) +""" + +import iris.plot as iplt +import iris.quickplot as qplt +import matplotlib.pyplot as plt +import numpy as np +import sub_functions as sf + + +def subplot_positions(j): + """Function to manually determine sub-plot positions in a 3x3 figure. + + Parameters + ---------- + j (int): index of cube position in cubelist + + Returns + ------- + x, y (int): subplot positions + """ + if j <= 2: + y = j + x = 0 + elif 2 < j <= 5: + y = j - 3 + x = 1 + else: + y = j - 6 + x = 2 + + return x, y + + +def plot_patterns(cube_list, plot_path): + """Plots climate patterns for imogen_mode: off. + + Parameters + ---------- + cube_list (cubelist): input cubelist for plotting patterns per variable + plot_path (path): path to plot_dir + + Returns + ------- + None + """ + fig, ax = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig.suptitle("Patterns from a random grid-cell", fontsize=18, y=0.98) + + plt.figure(figsize=(14, 12)) + plt.subplots_adjust(hspace=0.5) + plt.suptitle("Global Patterns, January", fontsize=18, y=0.95) + + for j, cube in enumerate(cube_list): + # determining plot positions + x, y = subplot_positions(j) + months = np.arange(1, 13) + # plots patterns for an arbitrary grid cell + ax[x, y].plot(months, cube[:, 50, 50].data) + ax[x, y].set_ylabel(str(cube.var_name) + " / " + str(cube.units)) + if j > 5: + ax[x, y].set_xlabel('Time') + + # January patterns + plt.subplot(3, 3, j + 1) + qplt.pcolormesh(cube[0]) + + plt.tight_layout() + plt.savefig(plot_path + 'Patterns') + plt.close() + + fig.tight_layout() + fig.savefig(plot_path + 'Patterns Timeseries') + + +def plot_cp_timeseries(list_cubelists, plot_path): + """Plots timeseries and maps of climatologies, anomalies and patterns. + + Parameters + ---------- + list_cubelists (cubelist): input cubelist for plotting per variable + plot_path (path): path to plot_dir + + Returns + ------- + None + """ + fig1, ax1 = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig1.suptitle("40 Year Climatologies, 1850-1889", fontsize=18, y=0.98) + + fig2, ax2 = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig2.suptitle("Anomaly Timeseries, 1850-2100", fontsize=18, y=0.98) + + fig3, ax3 = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig3.suptitle("Patterns from a random grid-cell", fontsize=18, y=0.98) + + plt.figure(figsize=(14, 12)) + plt.subplots_adjust(hspace=0.5) + plt.suptitle("Global Patterns, January", fontsize=18, y=0.95) + + for i, _ in enumerate(list_cubelists): + cube_list = list_cubelists[i] + for j, cube in enumerate(cube_list): + # determining plot positions + x, y = subplot_positions(j) + yrs = (1850 + np.arange(cube.shape[0])).astype('float') + if i == 0: + # climatology + avg_cube = sf.area_avg(cube, return_cube=False) + ax1[x, y].plot(yrs, avg_cube) + ax1[x, y].set_ylabel(cube.long_name + " / " + str(cube.units)) + if j > 5: + ax1[x, y].set_xlabel('Time') + if i == 1: + # anomaly timeseries + avg_cube = sf.area_avg(cube, return_cube=False) + ax2[x, y].plot(yrs, avg_cube) + ax2[x, y].set_ylabel(cube.long_name + " / " + str(cube.units)) + if j > 5: + ax2[x, y].set_xlabel('Time') + if i == 2: + months = np.arange(1, 13) + # avg_cube = sf.area_avg(cube, return_cube=False) + ax3[x, y].plot(months, cube[:, 50, 50].data) + ax3[x, y].set_ylabel( + str(cube.var_name) + " / " + str(cube.units)) + if j > 5: + ax3[x, y].set_xlabel('Time') + if i == 2: + # January patterns + plt.subplot(3, 3, j + 1) + qplt.pcolormesh(cube[0]) + + plt.tight_layout() + plt.savefig(plot_path + 'Patterns') + plt.close() + + fig1.tight_layout() + fig1.savefig(plot_path + 'Climatologies') + + fig2.tight_layout() + fig2.savefig(plot_path + 'Anomalies') + + fig3.tight_layout() + fig3.savefig(plot_path + 'Patterns Timeseries') + + +def plot_scores(cube_list, plot_path): + """Plots color mesh of scores per variable per month. + + Parameters + ---------- + cube_list (cube): input cubelist for plotting + plot_path (path): path to plot_dir + + Returns + ------- + None + """ + for cube in cube_list: + plt.figure(figsize=(14, 12)) + plt.subplots_adjust(hspace=0.5) + plt.suptitle("Scores " + cube.var_name, fontsize=18, y=0.98) + for j in range(0, 12): + plt.subplot(4, 3, j + 1) + qplt.pcolormesh(cube[j]) + + plt.tight_layout() + plt.savefig(plot_path + 'R2_Scores_' + str(cube.var_name)) + plt.close() + + # plot global scores timeseries per variable + plt.figure(figsize=(5, 8)) + for cube in cube_list: + score = sf.area_avg(cube, return_cube=True) + iplt.plot(score, label=cube.var_name) + plt.xlabel('Time') + plt.ylabel('R2 Score') + plt.legend(loc='center left') + + plt.savefig(plot_path + 'score_timeseries') + plt.close() diff --git a/esmvaltool/diag_scripts/climate_patterns_only/rename_variables.py b/esmvaltool/diag_scripts/climate_patterns_only/rename_variables.py new file mode 100644 index 0000000000..d8981a1517 --- /dev/null +++ b/esmvaltool/diag_scripts/climate_patterns_only/rename_variables.py @@ -0,0 +1,222 @@ +"""Script containing cube re-naming functions for main driving scripts, +dependent on recipe switch settings. + +Author +------ +Gregory Munday (Met Office, UK) +""" + + +def rename_clim_variables(cube): + """Rename variables and a coord to fit in JULES framework. + + Parameters + ---------- + cube (cube): input cube + + Returns + ------- + cube (cube): cube with renamed variables + """ + if cube.var_name == "tas": + cube.rename("Air Temperature") + cube.var_name = "tl1_clim" + if cube.var_name == "range_tl1": + cube.rename("Diurnal Range") + cube.var_name = "range_tl1_clim" + if cube.var_name == "hurs": + cube.rename("Relative Humidity") + cube.var_name = "rh1p5m_clim" + if cube.var_name == "huss": + cube.rename("Specific Humidity") + cube.var_name = "ql1_clim" + if cube.var_name == "pr": + cube.rename("Precipitation") + cube.var_name = "precip_clim" + if cube.var_name == "sfcWind": + cube.rename("Wind Speed") + cube.var_name = "wind_clim" + if cube.var_name == "ps": + cube.rename("Surface Pressure") + cube.var_name = "pstar_clim" + if cube.var_name == "rsds": + cube.rename("Surface Downwelling Shortwave Radiation") + cube.var_name = "swdown_clim" + if cube.var_name == "rlds": + cube.rename("Surface Downwelling Longwave Radiation") + cube.var_name = "lwdown_clim" + + cube.coord('month_number').rename('imogen_drive') + + return cube + + +def rename_anom_variables(cube): + """Rename variables and a coord to fit in JULES framework. + + Parameters + ---------- + cube (cube): input cube + + Returns + ------- + cube (cube): cube with renamed variables + """ + if cube.var_name == "tas": + cube.rename("Air Temperature") + cube.var_name = "tl1_anom" + if cube.var_name == "range_tl1": + cube.rename("Diurnal Range") + cube.var_name = "range_tl1_anom" + if cube.var_name == "hurs": + cube.rename("Relative Humidity") + cube.var_name = "rh1p5m_anom" + if cube.var_name == "huss": + cube.rename("Specific Humidity") + cube.var_name = "ql1_anom" + if cube.var_name == "pr": + cube.rename("Precipitation") + cube.var_name = "precip_anom" + if cube.var_name == "sfcWind": + cube.rename("Wind Speed") + cube.var_name = "wind_anom" + if cube.var_name == "ps": + cube.rename("Surface Pressure") + cube.var_name = "pstar_anom" + if cube.var_name == "rsds": + cube.rename("Surface Downwelling Shortwave Radiation") + cube.var_name = "swdown_anom" + if cube.var_name == "rlds": + cube.rename("Surface Downwelling Longwave Radiation") + cube.var_name = "lwdown_anom" + + cube.coord('month_number').rename('imogen_drive') + + return cube + + +def rename_variables(cube): + """Rename variables and a coord to fit in JULES framework. + + Parameters + ---------- + cube (cube): input cube + + Returns + ------- + cube (cube): cube with renamed variables + """ + if cube.var_name == "tas": + cube.rename("Air Temperature") + cube.var_name = "tl1" + if cube.var_name == "hurs": + cube.rename("Relative Humidity") + cube.var_name = "rh1p5m" + if cube.var_name == "huss": + cube.rename("Specific Humidity") + cube.var_name = "ql1" + if cube.var_name == "pr": + cube.rename("Precipitation") + cube.var_name = "precip" + if cube.var_name == "sfcWind": + cube.rename("Wind Speed") + cube.var_name = "wind" + if cube.var_name == "ps": + cube.rename("Surface Pressure") + cube.var_name = "pstar" + if cube.var_name == "rsds": + cube.rename("Surface Downwelling Shortwave Radiation") + cube.var_name = "swdown" + if cube.var_name == "rlds": + cube.rename("Surface Downwelling Longwave Radiation") + cube.var_name = "lwdown" + + cube.coord('month_number').rename('imogen_drive') + + return cube + + +def rename_regression_variables(cube): + """Rename variables to fit in JULES framework. + + Parameters + ---------- + cube (cube): input cube + + Returns + ------- + cube (cube): cube with renamed variables + """ + if cube.var_name == "tl1_anom": + cube.rename("Air Temperature") + cube.var_name = "tl1_patt" + if cube.var_name == "range_tl1_anom": + cube.rename("Diurnal Range") + cube.var_name = "range_tl1_patt" + if cube.var_name == "rh1p5m_anom": + cube.rename("Relative Humidity") + cube.var_name = "rh1p5m_patt" + if cube.var_name == "ql1_anom": + cube.rename("Specific Humidity") + cube.var_name = "ql1_patt" + if cube.var_name == "precip_anom": + cube.rename("Precipitation") + cube.var_name = "precip_patt" + if cube.var_name == "wind_anom": + cube.rename("Wind Speed") + cube.var_name = "wind_patt" + if cube.var_name == "pstar_anom": + cube.rename("Surface Pressure") + cube.var_name = "pstar_patt" + if cube.var_name == "swdown_anom": + cube.rename("Surface Downwelling Shortwave Radiation") + cube.var_name = "swdown_patt" + if cube.var_name == "lwdown_anom": + cube.rename("Surface Downwelling Longwave Radiation") + cube.var_name = "lwdown_patt" + + return cube + + +def rename_variables_base(cube): + """Rename variables and a coord for imogen_mode='off'. + + Parameters + ---------- + cube (cube): input cube + + Returns + ------- + cube (cube): cube with renamed variables + """ + if cube.var_name == "tl1_patt": + cube.rename("Air Temperature") + cube.var_name = "tas" + if cube.var_name == "range_tl1_patt": + cube.rename("Diurnal Range") + cube.var_name = "tas_range" + if cube.var_name == "rh1p5m_patt": + cube.rename("Relative Humidity") + cube.var_name = "hurs" + if cube.var_name == "ql1_patt": + cube.rename("Specific Humidity") + cube.var_name = "huss" + if cube.var_name == "precip_patt": + cube.rename("Precipitation") + cube.var_name = "pr" + if cube.var_name == "wind_patt": + cube.rename("Wind Speed") + cube.var_name = "sfcWind" + if cube.var_name == "pstar_patt": + cube.rename("Surface Pressure") + cube.var_name = "ps" + if cube.var_name == "swdown_patt": + cube.rename("Surface Downwelling Shortwave Radiation") + cube.var_name = "rsds" + if cube.var_name == "lwdown_patt": + cube.rename("Surface Downwelling Longwave Radiation") + cube.var_name = "rlds" + + cube.coord('imogen_drive').rename('month_number') + + return cube diff --git a/esmvaltool/diag_scripts/climate_patterns_only/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns_only/sub_functions.py new file mode 100644 index 0000000000..c2e7ab3036 --- /dev/null +++ b/esmvaltool/diag_scripts/climate_patterns_only/sub_functions.py @@ -0,0 +1,276 @@ +"""Script containing relevant sub-functions for main driving scripts. + +Author +------ +Gregory Munday (Met Office, UK) +""" + +import logging +import os +from pathlib import Path + +import iris +import iris.analysis.cartography +import iris.coord_categorisation +import numpy as np +from scipy.sparse.linalg import spsolve + +logger = logging.getLogger(Path(__file__).stem) + + +def compute_diagnostic(filename): + """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) + cube = iris.util.squeeze(cube) + + return cube + + +def area_avg(x, return_cube=None): + """Calculate the global mean of a variable in a cube, area-weighted. + + Parameters + ---------- + x (cube): input cube + return_cube (bool): option to return a cube or array + + Returns + ------- + x2 (cube): cube with collapsed lat-lons, global mean over time + x2.data (arr): array with collapsed lat-lons, global mean over time + """ + if not x.coord('latitude').has_bounds(): + x.coord('latitude').guess_bounds() + if not x.coord('longitude').has_bounds(): + x.coord('longitude').guess_bounds() + area = iris.analysis.cartography.area_weights(x, normalize=False) + x2 = x.collapsed(['latitude', 'longitude'], + iris.analysis.MEAN, + weights=area) + + if return_cube: + return x2 + else: + return x2.data + + +def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): + """Calculate the global mean of a variable in a cube, with options to be + land or ocean weighted. + + Parameters + ---------- + x (cube): input cube + ocean_frac (cube): ocean fraction cube, found from sftlf + land_frac (cube): land fraction cube, sftlf + land (bool): option to weight be land or ocean + return_cube (bool): option to return a cube or array + + Returns + ------- + x2 (cube): cube with collapsed lat-lons, global mean over time + x2.data (arr): array with collapsed lat-lons, global mean over time + """ + if not x.coord('latitude').has_bounds(): + x.coord('latitude').guess_bounds() + if not x.coord('longitude').has_bounds(): + x.coord('longitude').guess_bounds() + + global_weights = iris.analysis.cartography.area_weights(x, normalize=False) + + if land is False: + ocean_frac.data = np.ma.masked_less(ocean_frac.data, 0.01) + weights = iris.analysis.cartography.area_weights(ocean_frac, + normalize=False) + ocean_area = ocean_frac.collapsed(["latitude", "longitude"], + iris.analysis.SUM, + weights=weights) / 1e12 + print("Ocean area: ", ocean_area.data) + x2 = x.copy() + x2.data = x2.data * global_weights * ocean_frac.data + + x2 = x2.collapsed(['latitude', 'longitude'], + iris.analysis.SUM) / 1e12 / ocean_area.data + + if land: + land_frac.data = np.ma.masked_less(land_frac.data, 0.01) + weights = iris.analysis.cartography.area_weights(land_frac, + normalize=False) + land_area = land_frac.collapsed(["latitude", "longitude"], + iris.analysis.SUM, + weights=weights) / 1e12 + print("Land area: ", land_area.data) + x2 = x.copy() + x2.data = x2.data * global_weights * land_frac.data + x2 = x2.collapsed(['latitude', 'longitude'], + iris.analysis.SUM) / 1e12 / land_area.data + + if return_cube: + return x2 + else: + return x2.data + + +def kappa_calc_predict(q, f, kappa, lambda_o, lambda_l, nu): + """Energy balance model test function, which predicts ocean surface + temperature given the forcing, kappa, and existing atmospheric parameter + values. + + Parameters + ---------- + q (arr): derived effective radiative forcing + f (float): ocean fraction + kappa (float): ocean diffusivity parameter (W m-1 K-1) + lambda_o (float): climate sensitivity of land (W m-2 K-1) + lambda_l (float): climate sensitivity of ocean (W m-2 K-1) + nu_ratio (float): land-sea temperature contrast in warming + + Returns + ------- + temp_ocean_top (arr): ocean surface temp. (Huntingford & Cox, 2000) + """ + cp = 4.04E6 + nyr = q.shape[0] + n_pde = 20 + dt = (1.0 / float(n_pde)) * 60.0 * 60.0 * 24.0 * 365.0 + temp_ocean_top = np.zeros(nyr) + n_vert = 254 + depth = 5000.0 + dz = depth / float(n_vert) + + s = (kappa / cp) * (dt / (dz * dz)) + + t_ocean_old = np.zeros(n_vert + 1) + t_ocean_new = np.zeros(n_vert + 1) + + C = np.zeros([n_vert + 1, n_vert + 1]) + D = np.zeros([n_vert + 1, n_vert + 1]) + E = np.zeros(n_vert + 1) + + q_energy = 0.0 + + for j in range(0, nyr): + factor1 = -q[j] / (kappa * f) + factor2 = ( + (1.0 - f) * lambda_l * nu) / (f * kappa) + (lambda_o / kappa) + + for k in range(0, n_pde): + _ = j * n_pde + k + t_ocean_old = t_ocean_new + + C[0, 0] = s * (1.0 + dz * factor2) + 1 + C[0, 1] = -s + C[n_vert, n_vert - 1] = -s + C[n_vert, n_vert] = (1.0 + s) + for m in range(1, n_vert): + C[m, m - 1] = -s / 2.0 + C[m, m + 1] = -s / 2.0 + C[m, m] = 1.0 + s + + D[0, 0] = -s * (1.0 + dz * factor2) + 1 + D[0, 1] = s + D[n_vert, n_vert] = (1.0 - s) + D[n_vert, n_vert - 1] = s + for m in range(1, n_vert): + D[m, m - 1] = s / 2.0 + D[m, m + 1] = s / 2.0 + D[m, m] = 1.0 - s + + E[0] = -(kappa / cp) * (dt / dz) * (factor1 + factor1) + + C_sub = np.zeros(n_vert + 1) + C_main = np.zeros(n_vert + 1) + C_super = np.zeros(n_vert + 1) + for m in range(0, n_vert + 1): + C_main[m] = C[m, m] + for m in range(0, n_vert): + C_sub[m] = C[m + 1, m] + C_super[m] = C[m, m + 1] + + _ = np.mat(D) + e_mat = np.mat(E).transpose() + t_ocean_old_mat = np.mat(t_ocean_old).transpose() + b_rhs = np.dot(D, t_ocean_old_mat) + e_mat + b_rhs = np.ravel(b_rhs) + + t_ocean_new = spsolve(C, b_rhs) + + q_energy = q_energy + dt * kappa * (-factor1 - + (t_ocean_new[0] * factor2)) + + temp_ocean_top[j] = t_ocean_new[0] + + q_energy_derived = 0.0 + for i in range(1, n_vert + 1): + q_energy_derived = q_energy_derived + cp * 0.5 * ( + t_ocean_new[i] + t_ocean_new[i - 1]) * dz + + conserved = 100.0 * (q_energy_derived / q_energy) + logger.info("Heat conservation check (%): ", round(conserved, 2)) + + return temp_ocean_top + + +def make_model_dirs(cube_initial, work_path, plot_path): + """Create directories for each input model for saving. + + Parameters + ---------- + cube_initial (cube): initial input cube used to retrieve model name + work_path (path): path to work_dir + plot_path (path): path to plot_dir + + 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 + """ + w_path = os.path.join(work_path, cube_initial.attributes['source_id']) + p_path = os.path.join(plot_path, cube_initial.attributes['source_id']) + os.mkdir(w_path) + os.mkdir(p_path) + + model_work_dir = w_path + "/" + model_plot_dir = p_path + "/" + + return model_work_dir, model_plot_dir + + +def parallelise(f, processes=None): + """Wrapper to parallelise any function, graciously supplied by George Ford + (Met Office). + + Parameters + ---------- + f (func): function to be parallelised + processes (int): number of threads to be used in parallelisation + + Returns + ------- + result (any): results of parallelised elements + """ + import multiprocessing as mp + if processes is None: + processes = max(1, mp.cpu_count() - 1) + if processes <= 0: + processes = 1 + + def easy_parallise(f, sequence): + pool = mp.Pool(processes=processes) + result = pool.map_async(f, sequence).get() + pool.close() + pool.join() + return result + + from functools import partial + return partial(easy_parallise, f) From ca5cc121c09473ceddef7afb571405e16a272da0 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 11:19:55 +0100 Subject: [PATCH 03/70] Brought everything from only branch --- .../figures/climate_patterns/patterns.png | Bin 0 -> 184589 bytes .../climate_patterns/score_timeseries.png | Bin 0 -> 54711 bytes esmvaltool/config-references.yml | 5 +++++ .../climate_patterns.py | 0 .../cp_plotting.py | 0 .../rename_variables.py | 0 .../sub_functions.py | 0 ...e_patterns_only => recipe_climate_patterns} | 0 8 files changed, 5 insertions(+) create mode 100644 doc/sphinx/source/recipes/figures/climate_patterns/patterns.png create mode 100644 doc/sphinx/source/recipes/figures/climate_patterns/score_timeseries.png rename esmvaltool/diag_scripts/{climate_patterns_only => climate_patterns}/climate_patterns.py (100%) rename esmvaltool/diag_scripts/{climate_patterns_only => climate_patterns}/cp_plotting.py (100%) rename esmvaltool/diag_scripts/{climate_patterns_only => climate_patterns}/rename_variables.py (100%) rename esmvaltool/diag_scripts/{climate_patterns_only => climate_patterns}/sub_functions.py (100%) rename esmvaltool/recipes/{recipe_climate_patterns_only => recipe_climate_patterns} (100%) diff --git a/doc/sphinx/source/recipes/figures/climate_patterns/patterns.png b/doc/sphinx/source/recipes/figures/climate_patterns/patterns.png new file mode 100644 index 0000000000000000000000000000000000000000..396fd788302f32393bb3fc4e29ef6ee4812d3b5c GIT binary patch literal 184589 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

H)5r+ngrV+ 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 nkp92c8T-G{0sQ~xGjBo!S{vud$xa Date: Thu, 1 Sep 2022 11:22:30 +0100 Subject: [PATCH 04/70] Quick fix of sub_functions.py --- .../climate_patterns/sub_functions.py | 102 ------------------ 1 file changed, 102 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index c2e7ab3036..0c0b46f45b 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -13,11 +13,9 @@ import iris.analysis.cartography import iris.coord_categorisation import numpy as np -from scipy.sparse.linalg import spsolve logger = logging.getLogger(Path(__file__).stem) - def compute_diagnostic(filename): """Load cube, remove any dimensions of length: 1. @@ -121,106 +119,6 @@ def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): return x2.data -def kappa_calc_predict(q, f, kappa, lambda_o, lambda_l, nu): - """Energy balance model test function, which predicts ocean surface - temperature given the forcing, kappa, and existing atmospheric parameter - values. - - Parameters - ---------- - q (arr): derived effective radiative forcing - f (float): ocean fraction - kappa (float): ocean diffusivity parameter (W m-1 K-1) - lambda_o (float): climate sensitivity of land (W m-2 K-1) - lambda_l (float): climate sensitivity of ocean (W m-2 K-1) - nu_ratio (float): land-sea temperature contrast in warming - - Returns - ------- - temp_ocean_top (arr): ocean surface temp. (Huntingford & Cox, 2000) - """ - cp = 4.04E6 - nyr = q.shape[0] - n_pde = 20 - dt = (1.0 / float(n_pde)) * 60.0 * 60.0 * 24.0 * 365.0 - temp_ocean_top = np.zeros(nyr) - n_vert = 254 - depth = 5000.0 - dz = depth / float(n_vert) - - s = (kappa / cp) * (dt / (dz * dz)) - - t_ocean_old = np.zeros(n_vert + 1) - t_ocean_new = np.zeros(n_vert + 1) - - C = np.zeros([n_vert + 1, n_vert + 1]) - D = np.zeros([n_vert + 1, n_vert + 1]) - E = np.zeros(n_vert + 1) - - q_energy = 0.0 - - for j in range(0, nyr): - factor1 = -q[j] / (kappa * f) - factor2 = ( - (1.0 - f) * lambda_l * nu) / (f * kappa) + (lambda_o / kappa) - - for k in range(0, n_pde): - _ = j * n_pde + k - t_ocean_old = t_ocean_new - - C[0, 0] = s * (1.0 + dz * factor2) + 1 - C[0, 1] = -s - C[n_vert, n_vert - 1] = -s - C[n_vert, n_vert] = (1.0 + s) - for m in range(1, n_vert): - C[m, m - 1] = -s / 2.0 - C[m, m + 1] = -s / 2.0 - C[m, m] = 1.0 + s - - D[0, 0] = -s * (1.0 + dz * factor2) + 1 - D[0, 1] = s - D[n_vert, n_vert] = (1.0 - s) - D[n_vert, n_vert - 1] = s - for m in range(1, n_vert): - D[m, m - 1] = s / 2.0 - D[m, m + 1] = s / 2.0 - D[m, m] = 1.0 - s - - E[0] = -(kappa / cp) * (dt / dz) * (factor1 + factor1) - - C_sub = np.zeros(n_vert + 1) - C_main = np.zeros(n_vert + 1) - C_super = np.zeros(n_vert + 1) - for m in range(0, n_vert + 1): - C_main[m] = C[m, m] - for m in range(0, n_vert): - C_sub[m] = C[m + 1, m] - C_super[m] = C[m, m + 1] - - _ = np.mat(D) - e_mat = np.mat(E).transpose() - t_ocean_old_mat = np.mat(t_ocean_old).transpose() - b_rhs = np.dot(D, t_ocean_old_mat) + e_mat - b_rhs = np.ravel(b_rhs) - - t_ocean_new = spsolve(C, b_rhs) - - q_energy = q_energy + dt * kappa * (-factor1 - - (t_ocean_new[0] * factor2)) - - temp_ocean_top[j] = t_ocean_new[0] - - q_energy_derived = 0.0 - for i in range(1, n_vert + 1): - q_energy_derived = q_energy_derived + cp * 0.5 * ( - t_ocean_new[i] + t_ocean_new[i - 1]) * dz - - conserved = 100.0 * (q_energy_derived / q_energy) - logger.info("Heat conservation check (%): ", round(conserved, 2)) - - return temp_ocean_top - - def make_model_dirs(cube_initial, work_path, plot_path): """Create directories for each input model for saving. From 51c9ac5a02ab59c2a1795069fdffe48cf760cdee Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 11:28:43 +0100 Subject: [PATCH 05/70] blacked all my files --- .../climate_patterns/climate_patterns.py | 139 ++++++++++-------- .../climate_patterns/cp_plotting.py | 32 ++-- .../climate_patterns/rename_variables.py | 8 +- .../climate_patterns/sub_functions.py | 45 +++--- 4 files changed, 122 insertions(+), 102 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 704b180642..aef38110fb 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -43,11 +43,7 @@ import numpy as np import sklearn.linear_model import sub_functions as sf -from cp_plotting import ( - plot_cp_timeseries, - plot_patterns, - plot_scores, -) +from cp_plotting import plot_cp_timeseries, plot_patterns, plot_scores from rename_variables import ( rename_anom_variables, rename_clim_variables, @@ -65,7 +61,7 @@ def climatology(cube, syr=1850, eyr=1889): Parameters ---------- - cube : cube + cube : cube cube loaded from config dictionary Returns @@ -74,8 +70,10 @@ def climatology(cube, syr=1850, eyr=1889): (default 1850-1889) """ cube_40yr = cube.extract( - iris.Constraint(time=lambda t: syr <= t.point.year <= eyr, - month_number=lambda t: 1 <= t.point <= 12)) + iris.Constraint( + time=lambda t: syr <= t.point.year <= eyr, + month_number=lambda t: 1 <= t.point <= 12, + )) cube_aggregated = make_monthly_climatology(cube_40yr) return cube_aggregated @@ -138,7 +136,7 @@ def diurnal_temp_range(cubelist): range_cube = -range_cube range_cube.rename("Diurnal Range") - range_cube.var_name = ("range_tl1") + range_cube.var_name = "range_tl1" return range_cube @@ -164,11 +162,9 @@ def calculate_diurnal_range(clim_list, ts_list): for i, _ in enumerate(comb_list): for cube in comb_list[i]: - if (cube.var_name in ('tasmax', 'tasmin')) \ - and cube in clim_list: + if (cube.var_name in ("tasmax", "tasmin")) and cube in clim_list: temp_range_list_clim.append(cube) - elif (cube.var_name in ('tasmax', 'tasmin')) \ - and cube in ts_list: + elif (cube.var_name in ("tasmax", "tasmin")) and cube in ts_list: temp_range_list_ts.append(cube) else: pass @@ -207,11 +203,11 @@ def append_diurnal_range(derived_diurnal_clim, derived_diurnal_ts, clim_list, ts_list_final = iris.cube.CubeList([]) for cube in clim_list: - if cube.var_name not in ('tasmax', 'tasmin'): + if cube.var_name not in ("tasmax", "tasmin"): clim_list_final.append(cube) for cube in ts_list: - if cube.var_name not in ('tasmax', 'tasmin'): + if cube.var_name not in ("tasmax", "tasmin"): ts_list_final.append(cube) clim_list_final.append(derived_diurnal_clim) @@ -242,16 +238,16 @@ def calculate_anomaly(clim_list, ts_list): # calc the anom by subtracting the monthly climatology from # the time series for i, _ in enumerate(ts_list_final): - i_months = anom_list_final[i].coord( - 'month_number').points - 1 # -1 because months are numbered 1..12 + i_months = (anom_list_final[i].coord("month_number").points - 1 + ) # -1 because months are numbered 1..12 anom_list_final[i].data -= clim_list_final[i][i_months].data return clim_list_final, anom_list_final def regression(tas, cube_data): - """Calculate the coefficients of regression between global surface temp - and a variable. + """Calculate the coefficients of regression between global surface temp and + a variable. Parameters ---------- @@ -299,16 +295,16 @@ def regression_units(tas, cube): ------- units (str): string of calculated regression units """ - print('Cube Units: ', cube.units) + print("Cube Units: ", cube.units) units = cube.units / tas.units - print('Regression Units: ', units) + print("Regression Units: ", units) return units def calculate_regressions(anom_list, yrs=85): - """Facilitate the calculation of regression coefficients (climate - patterns) and the creation of a new cube of patterns per variable. + """Facilitate the calculation of regression coefficients (climate patterns) + and the creation of a new cube of patterns per variable. Parameters ---------- @@ -326,7 +322,7 @@ def calculate_regressions(anom_list, yrs=85): months = yrs * 12 for cube in anom_list: - if cube.var_name == 'tl1_anom': + if cube.var_name == "tl1_anom": # convert years to months when selecting tas = cube[-months:] @@ -346,8 +342,8 @@ def calculate_regressions(anom_list, yrs=85): month_cube_ssp.data) # re-creating cube - if cube.var_name in ('swdown_anom', 'lwdown_anom'): - units = 'W m-2 K-1' + if cube.var_name in ("swdown_anom", "lwdown_anom"): + units = "W m-2 K-1" else: units = regression_units(tas, cube_ssp) @@ -357,27 +353,30 @@ def calculate_regressions(anom_list, yrs=85): dim_coords_and_dims = [(coord1, 0), (coord2, 1)] # assigning aux_coord - coord_month = iris.coords.AuxCoord(i, var_name='imogen_drive') + coord_month = iris.coords.AuxCoord(i, var_name="imogen_drive") aux_coords_and_dims = [(coord_month, ())] cube = rename_regression_variables(cube) # creating cube of regression values - regr_cube = iris.cube.Cube(regr_array, - units=units, - dim_coords_and_dims=dim_coords_and_dims, - aux_coords_and_dims=aux_coords_and_dims, - var_name=cube.var_name, - standard_name=cube.standard_name) + regr_cube = iris.cube.Cube( + regr_array, + units=units, + dim_coords_and_dims=dim_coords_and_dims, + aux_coords_and_dims=aux_coords_and_dims, + var_name=cube.var_name, + standard_name=cube.standard_name, + ) # calculating cube of r2 scores score_cube = iris.cube.Cube( score_array, - units='R2', + units="R2", dim_coords_and_dims=dim_coords_and_dims, aux_coords_and_dims=aux_coords_and_dims, var_name=cube.var_name, - standard_name=cube.standard_name) + standard_name=cube.standard_name, + ) month_list.append(regr_cube) score_month_list.append(score_cube) @@ -408,10 +407,10 @@ def write_scores(scores, work_path): mean_score = np.mean(score) # saving scores - file = open(work_path + 'scores', 'a') - data = '{0:10.3f}'.format(mean_score) + file = open(work_path + "scores", "a") + data = "{0:10.3f}".format(mean_score) name = cube.var_name - file.write(name + ': ' + data + '\n') + file.write(name + ": " + data + "\n") file.close() @@ -429,28 +428,36 @@ def cube_saver(list_of_cubelists, work_path, name_list, mode): ------- None """ - if mode == 'imogen_scores': + if mode == "imogen_scores": for i in range(0, 4): iris.save(list_of_cubelists[i], work_path + name_list[i]) - if mode == 'imogen': + if mode == "imogen": for i in range(0, 3): iris.save(list_of_cubelists[i], work_path + name_list[i]) - if mode == 'scores': + if mode == "scores": for i in range(2, 4): for cube in list_of_cubelists[i]: rename_variables_base(cube) iris.save(list_of_cubelists[i], work_path + name_list[i]) - if mode == 'base': + if mode == "base": for cube in list_of_cubelists[2]: rename_variables_base(cube) iris.save(list_of_cubelists[2], work_path + name_list[2]) -def saving_outputs(clim_list_final, anom_list_final, regressions, scores, - imogen_mode, r2_scores, plot_path, work_path): +def saving_outputs( + clim_list_final, + anom_list_final, + regressions, + scores, + imogen_mode, + r2_scores, + plot_path, + work_path, +): """Save data and plots to relevant directories. Parameters @@ -466,8 +473,10 @@ def saving_outputs(clim_list_final, anom_list_final, regressions, scores, """ list_of_cubelists = [clim_list_final, anom_list_final, regressions, scores] name_list = [ - "climatology_variables.nc", "anomaly_variables.nc", "patterns.nc", - "scores.nc" + "climatology_variables.nc", + "anomaly_variables.nc", + "patterns.nc", + "scores.nc", ] # saving data + plotting @@ -479,22 +488,22 @@ def saving_outputs(clim_list_final, anom_list_final, regressions, scores, cube_saver(list_of_cubelists, work_path, name_list, - mode='imogen_scores') + mode="imogen_scores") else: plot_cp_timeseries(list_of_cubelists, plot_path) - cube_saver(list_of_cubelists, work_path, name_list, mode='imogen') + cube_saver(list_of_cubelists, work_path, name_list, mode="imogen") else: if r2_scores is True: plot_scores(list_of_cubelists[3], plot_path) write_scores(scores, work_path) plot_patterns(list_of_cubelists[2], plot_path) - cube_saver(list_of_cubelists, work_path, name_list, mode='scores') + cube_saver(list_of_cubelists, work_path, name_list, mode="scores") else: plot_patterns(list_of_cubelists[2], plot_path) - cube_saver(list_of_cubelists, work_path, name_list, mode='base') + cube_saver(list_of_cubelists, work_path, name_list, mode="base") def get_provenance_record(): @@ -509,12 +518,12 @@ def get_provenance_record(): record (dict): provenance record """ record = { - 'caption': ['Generating Climate Patterns from CMIP6 Models'], - 'statistics': ['mean', 'other'], - 'domains': ['global'], - 'themes': ['carbon'], - 'realms': ['atmos'], - 'authors': ['munday_gregory'], + "caption": ["Generating Climate Patterns from CMIP6 Models"], + "statistics": ["mean", "other"], + "domains": ["global"], + "themes": ["carbon"], + "realms": ["atmos"], + "authors": ["munday_gregory"], } return record @@ -543,7 +552,7 @@ def patterns(model): ts_list = iris.cube.CubeList([]) for dataset in input_data: - if dataset['dataset'] == model: + if dataset["dataset"] == model: input_file = dataset["filename"] # preparing single cube @@ -573,11 +582,19 @@ def patterns(model): model_work_dir, model_plot_dir = sf.make_model_dirs( cube_initial, work_path, plot_path) - saving_outputs(clim_list_final, anom_list_final, regressions, scores, - imogen_mode, r2_scores, model_plot_dir, model_work_dir) + saving_outputs( + clim_list_final, + anom_list_final, + regressions, + scores, + imogen_mode, + r2_scores, + model_plot_dir, + model_work_dir, + ) provenance_record = get_provenance_record() - path = model_work_dir + 'patterns.nc' + path = model_work_dir + "patterns.nc" with ProvenanceLogger(cfg) as provenance_logger: provenance_logger.log(path, provenance_record) @@ -599,7 +616,7 @@ def main(cfg): models = [] for x in input_data: - model = x['dataset'] + model = x["dataset"] if model not in models: models.append(model) diff --git a/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py b/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py index 411a1d1834..e6e386e487 100644 --- a/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py @@ -63,18 +63,18 @@ def plot_patterns(cube_list, plot_path): ax[x, y].plot(months, cube[:, 50, 50].data) ax[x, y].set_ylabel(str(cube.var_name) + " / " + str(cube.units)) if j > 5: - ax[x, y].set_xlabel('Time') + ax[x, y].set_xlabel("Time") # January patterns plt.subplot(3, 3, j + 1) qplt.pcolormesh(cube[0]) plt.tight_layout() - plt.savefig(plot_path + 'Patterns') + plt.savefig(plot_path + "Patterns") plt.close() fig.tight_layout() - fig.savefig(plot_path + 'Patterns Timeseries') + fig.savefig(plot_path + "Patterns Timeseries") def plot_cp_timeseries(list_cubelists, plot_path): @@ -107,21 +107,21 @@ def plot_cp_timeseries(list_cubelists, plot_path): for j, cube in enumerate(cube_list): # determining plot positions x, y = subplot_positions(j) - yrs = (1850 + np.arange(cube.shape[0])).astype('float') + yrs = (1850 + np.arange(cube.shape[0])).astype("float") if i == 0: # climatology avg_cube = sf.area_avg(cube, return_cube=False) ax1[x, y].plot(yrs, avg_cube) ax1[x, y].set_ylabel(cube.long_name + " / " + str(cube.units)) if j > 5: - ax1[x, y].set_xlabel('Time') + ax1[x, y].set_xlabel("Time") if i == 1: # anomaly timeseries avg_cube = sf.area_avg(cube, return_cube=False) ax2[x, y].plot(yrs, avg_cube) ax2[x, y].set_ylabel(cube.long_name + " / " + str(cube.units)) if j > 5: - ax2[x, y].set_xlabel('Time') + ax2[x, y].set_xlabel("Time") if i == 2: months = np.arange(1, 13) # avg_cube = sf.area_avg(cube, return_cube=False) @@ -129,24 +129,24 @@ def plot_cp_timeseries(list_cubelists, plot_path): ax3[x, y].set_ylabel( str(cube.var_name) + " / " + str(cube.units)) if j > 5: - ax3[x, y].set_xlabel('Time') + ax3[x, y].set_xlabel("Time") if i == 2: # January patterns plt.subplot(3, 3, j + 1) qplt.pcolormesh(cube[0]) plt.tight_layout() - plt.savefig(plot_path + 'Patterns') + plt.savefig(plot_path + "Patterns") plt.close() fig1.tight_layout() - fig1.savefig(plot_path + 'Climatologies') + fig1.savefig(plot_path + "Climatologies") fig2.tight_layout() - fig2.savefig(plot_path + 'Anomalies') + fig2.savefig(plot_path + "Anomalies") fig3.tight_layout() - fig3.savefig(plot_path + 'Patterns Timeseries') + fig3.savefig(plot_path + "Patterns Timeseries") def plot_scores(cube_list, plot_path): @@ -170,7 +170,7 @@ def plot_scores(cube_list, plot_path): qplt.pcolormesh(cube[j]) plt.tight_layout() - plt.savefig(plot_path + 'R2_Scores_' + str(cube.var_name)) + plt.savefig(plot_path + "R2_Scores_" + str(cube.var_name)) plt.close() # plot global scores timeseries per variable @@ -178,9 +178,9 @@ def plot_scores(cube_list, plot_path): for cube in cube_list: score = sf.area_avg(cube, return_cube=True) iplt.plot(score, label=cube.var_name) - plt.xlabel('Time') - plt.ylabel('R2 Score') - plt.legend(loc='center left') + plt.xlabel("Time") + plt.ylabel("R2 Score") + plt.legend(loc="center left") - plt.savefig(plot_path + 'score_timeseries') + plt.savefig(plot_path + "score_timeseries") plt.close() diff --git a/esmvaltool/diag_scripts/climate_patterns/rename_variables.py b/esmvaltool/diag_scripts/climate_patterns/rename_variables.py index d8981a1517..a7ca12c683 100644 --- a/esmvaltool/diag_scripts/climate_patterns/rename_variables.py +++ b/esmvaltool/diag_scripts/climate_patterns/rename_variables.py @@ -46,7 +46,7 @@ def rename_clim_variables(cube): cube.rename("Surface Downwelling Longwave Radiation") cube.var_name = "lwdown_clim" - cube.coord('month_number').rename('imogen_drive') + cube.coord("month_number").rename("imogen_drive") return cube @@ -90,7 +90,7 @@ def rename_anom_variables(cube): cube.rename("Surface Downwelling Longwave Radiation") cube.var_name = "lwdown_anom" - cube.coord('month_number').rename('imogen_drive') + cube.coord("month_number").rename("imogen_drive") return cube @@ -131,7 +131,7 @@ def rename_variables(cube): cube.rename("Surface Downwelling Longwave Radiation") cube.var_name = "lwdown" - cube.coord('month_number').rename('imogen_drive') + cube.coord("month_number").rename("imogen_drive") return cube @@ -217,6 +217,6 @@ def rename_variables_base(cube): cube.rename("Surface Downwelling Longwave Radiation") cube.var_name = "rlds" - cube.coord('imogen_drive').rename('month_number') + cube.coord("imogen_drive").rename("month_number") return cube diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 0c0b46f45b..17e316dc05 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -16,6 +16,7 @@ logger = logging.getLogger(Path(__file__).stem) + def compute_diagnostic(filename): """Load cube, remove any dimensions of length: 1. @@ -47,12 +48,12 @@ def area_avg(x, return_cube=None): x2 (cube): cube with collapsed lat-lons, global mean over time x2.data (arr): array with collapsed lat-lons, global mean over time """ - if not x.coord('latitude').has_bounds(): - x.coord('latitude').guess_bounds() - if not x.coord('longitude').has_bounds(): - x.coord('longitude').guess_bounds() + if not x.coord("latitude").has_bounds(): + x.coord("latitude").guess_bounds() + if not x.coord("longitude").has_bounds(): + x.coord("longitude").guess_bounds() area = iris.analysis.cartography.area_weights(x, normalize=False) - x2 = x.collapsed(['latitude', 'longitude'], + x2 = x.collapsed(["latitude", "longitude"], iris.analysis.MEAN, weights=area) @@ -79,10 +80,10 @@ def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): x2 (cube): cube with collapsed lat-lons, global mean over time x2.data (arr): array with collapsed lat-lons, global mean over time """ - if not x.coord('latitude').has_bounds(): - x.coord('latitude').guess_bounds() - if not x.coord('longitude').has_bounds(): - x.coord('longitude').guess_bounds() + if not x.coord("latitude").has_bounds(): + x.coord("latitude").guess_bounds() + if not x.coord("longitude").has_bounds(): + x.coord("longitude").guess_bounds() global_weights = iris.analysis.cartography.area_weights(x, normalize=False) @@ -90,28 +91,28 @@ def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): ocean_frac.data = np.ma.masked_less(ocean_frac.data, 0.01) weights = iris.analysis.cartography.area_weights(ocean_frac, normalize=False) - ocean_area = ocean_frac.collapsed(["latitude", "longitude"], - iris.analysis.SUM, - weights=weights) / 1e12 + ocean_area = (ocean_frac.collapsed( + ["latitude", "longitude"], iris.analysis.SUM, weights=weights) / + 1e12) print("Ocean area: ", ocean_area.data) x2 = x.copy() x2.data = x2.data * global_weights * ocean_frac.data - x2 = x2.collapsed(['latitude', 'longitude'], - iris.analysis.SUM) / 1e12 / ocean_area.data + x2 = (x2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / + 1e12 / ocean_area.data) if land: land_frac.data = np.ma.masked_less(land_frac.data, 0.01) weights = iris.analysis.cartography.area_weights(land_frac, normalize=False) - land_area = land_frac.collapsed(["latitude", "longitude"], - iris.analysis.SUM, - weights=weights) / 1e12 + land_area = (land_frac.collapsed( + ["latitude", "longitude"], iris.analysis.SUM, weights=weights) / + 1e12) print("Land area: ", land_area.data) x2 = x.copy() x2.data = x2.data * global_weights * land_frac.data - x2 = x2.collapsed(['latitude', 'longitude'], - iris.analysis.SUM) / 1e12 / land_area.data + x2 = (x2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / + 1e12 / land_area.data) if return_cube: return x2 @@ -133,8 +134,8 @@ def make_model_dirs(cube_initial, work_path, plot_path): model_work_dir (path): path to specific model directory in work_dir model_plot_dir (path): path to specific plot directory in plot_dir """ - w_path = os.path.join(work_path, cube_initial.attributes['source_id']) - p_path = os.path.join(plot_path, cube_initial.attributes['source_id']) + w_path = os.path.join(work_path, cube_initial.attributes["source_id"]) + p_path = os.path.join(plot_path, cube_initial.attributes["source_id"]) os.mkdir(w_path) os.mkdir(p_path) @@ -158,6 +159,7 @@ def parallelise(f, processes=None): result (any): results of parallelised elements """ import multiprocessing as mp + if processes is None: processes = max(1, mp.cpu_count() - 1) if processes <= 0: @@ -171,4 +173,5 @@ def easy_parallise(f, sequence): return result from functools import partial + return partial(easy_parallise, f) From 1e422f6cbeae34415dcbedd592acf216222c52c5 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 11:42:38 +0100 Subject: [PATCH 06/70] testing documentation fix --- .../climate_patterns/climate_patterns.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index aef38110fb..90ff8b617e 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -57,7 +57,7 @@ def climatology(cube, syr=1850, eyr=1889): - """Handles aggregation to make climatology. + """Handle aggregation to make climatology. Parameters ---------- @@ -66,8 +66,8 @@ def climatology(cube, syr=1850, eyr=1889): Returns ------- - cube_aggregated (cube): 40 year climatology cube from syr-eyr - (default 1850-1889) + cube_aggregated : cube + 40 year climatology cube from syr-eyr (default 1850-1889) """ cube_40yr = cube.extract( iris.Constraint( @@ -84,13 +84,17 @@ def constrain_latitude(cube, min_lat=-55, max_lat=82.5): Parameters ---------- - cube (cube): cube loaded from config dictionary - min_lat (float): minimum latitude to crop - max_lat (float): maximum latitude to crop + cube : cube + cube loaded from config dictionary + min_lat : float + minimum latitude to crop + max_lat : float + maximum latitude to crop Returns ------- - cube_clipped (cube): cube with latitudes set from -55 to 82.5 degrees + cube_clipped : cube + cube with latitudes set from -55 to 82.5 degrees """ cube_clipped = cube.extract( iris.Constraint(latitude=lambda cell: max_lat >= cell >= min_lat)) From da9c7c2526d82d5a4e422b8eb40c8259028bad60 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 11:50:46 +0100 Subject: [PATCH 07/70] another docstring test --- esmvaltool/diag_scripts/climate_patterns/climate_patterns.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 90ff8b617e..519c71f332 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -63,6 +63,10 @@ def climatology(cube, syr=1850, eyr=1889): ---------- cube : cube cube loaded from config dictionary + syr : int + set climatology start year + eyr : int + set climatology end year Returns ------- From a8ead28ff5a89f5e9a76812a0144fd55c01d00f3 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 12:14:41 +0100 Subject: [PATCH 08/70] climate_patterns.py docstring fixes --- .../climate_patterns/climate_patterns.py | 149 +++++++++++------- 1 file changed, 92 insertions(+), 57 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 519c71f332..14c729e9aa 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -111,11 +111,13 @@ def make_monthly_climatology(cube): Parameters ---------- - cube (cube): cube loaded from config dictionary + cube : cube + cube loaded from config dictionary Returns ------- - cube_month_climatol (cube): cube aggregated by month_number + cube_month_climatol : cube + cube aggregated by month_number """ if not cube.coords("month_number"): iris.coord_categorisation.add_month_number(cube, "time", @@ -131,11 +133,13 @@ def diurnal_temp_range(cubelist): Parameters ---------- - cubelist (cubelist): cubelist of tasmin and tasmax + cubelist : cubelist + cubelist of tasmin and tasmax Returns ------- - range_cube (cube): cube of calculated diurnal range + range_cube : cube + cube of calculated diurnal range """ range_cube = cubelist[0] - cubelist[1] @@ -154,15 +158,17 @@ def calculate_diurnal_range(clim_list, ts_list): Parameters ---------- - clim_list (cubelist): cubelist of climatology cubes - ts_list (cubelist): cubelist of standard timeseries cubes + clim_list : cubelist + cubelist of climatology cubes + ts_list : cubelist + cubelist of standard timeseries cubes Returns ------- - clim_list_final (cubelist): cubelist of climatology cubes - including diurnal range - ts_list_final (cubelist): cubelist of standard timeseries cubes - including diurnal range + clim_list_final : cubelist + cubelist of climatology cubes including diurnal range + ts_list_final : cubelist + cubelist of standard timeseries cubes including diurnal range """ temp_range_list_clim = iris.cube.CubeList([]) temp_range_list_ts = iris.cube.CubeList([]) @@ -193,17 +199,21 @@ def append_diurnal_range(derived_diurnal_clim, derived_diurnal_ts, clim_list, Parameters ---------- - derived_diurnal_clim (cube): derived diurnal climatology cube - derived_diurnal_ts (cube): derived diurnal timeseries cube - clim_list (cubelist): existing climatology cubelist, no range - ts_list (cubelist): existing timeseries cubelist, no range + derived_diurnal_clim : cube + derived diurnal climatology cube + derived_diurnal_ts : cube + derived diurnal timeseries cube + clim_list : cubelist + existing climatology cubelist, no range + ts_list : cubelist + existing timeseries cubelist, no range Returns ------- - clim_list_final (cubelist): cubelist of climatology cubes - including diurnal range - ts_list_final (cubelist): cubelist of standard timeseries cubes - including diurnal range + clim_list_final : cubelist + cubelist of climatology cubes including diurnal range + ts_list_final : cubelist + cubelist of standard timeseries cubes including diurnal range """ # creating cube list without tasmax or tasmin # (since we just wanted the diurnal range) @@ -229,13 +239,17 @@ def calculate_anomaly(clim_list, ts_list): Parameters ---------- - clim_list (cubelist): cubelist of climatology variables - ts_list (cubelist): cubelist of standard variable timeseries + clim_list : cubelist + cubelist of climatology variables + ts_list : cubelist + cubelist of standard variable timeseries Returns ------- - clim_list_final (cubelist): cubelist of clim. vars, inc. diurnal range - anom_list_final (cubelist): cubelist of anomaly vars, inc. diurnal range + clim_list_final : cubelist + cubelist of clim. vars, inc. diurnal range + anom_list_final : cubelist + cubelist of anomaly vars, inc. diurnal range """ # calculate diurnal temperature range cube clim_list_final, ts_list_final = calculate_diurnal_range( @@ -254,19 +268,22 @@ def calculate_anomaly(clim_list, ts_list): def regression(tas, cube_data): - """Calculate the coefficients of regression between global surface temp and - a variable. + """Calculate coeffs of regression between global surf temp and variable. Parameters ---------- - tas (cube): near-surface air temperature - cube_data (arr): cube.data array of a variable + tas : cube + near-surface air temperature + cube_data : arr + cube.data array of a variable Returns ------- - slope_array (arr): array of grid cells with same shape as initial cube, + slope_array : arr + array of grid cells with same shape as initial cube, containing the regression slope - score_array (arr): array of grid cells with same shape as initial cube, + score_array : arr + array of grid cells with same shape as initial cube, containing the regression score as a measure of robustness """ slope_array = np.full(tas.data.shape[1:], np.nan) @@ -281,12 +298,12 @@ def regression(tas, cube_data): model = sklearn.linear_model.LinearRegression( fit_intercept=False, copy_X=True) - x = tas_data.reshape(-1, 1) - y = cube_data[:, i, j] + x_val = tas_data.reshape(-1, 1) + y_val = cube_data[:, i, j] - model.fit(x, y) + model.fit(x_val, y_val) slope_array[i, j] = model.coef_ - score_array[i, j] = model.score(x, y) + score_array[i, j] = model.score(x_val, y_val) return slope_array, score_array @@ -296,12 +313,15 @@ def regression_units(tas, cube): Parameters ---------- - tas (cube): near-surface air temperature - cube (cube): cube of a given variable + tas : cube + near-surface air temperature + cube : cube + cube of a given variable Returns ------- - units (str): string of calculated regression units + units : str + string of calculated regression units """ print("Cube Units: ", cube.units) units = cube.units / tas.units @@ -311,19 +331,21 @@ def regression_units(tas, cube): def calculate_regressions(anom_list, yrs=85): - """Facilitate the calculation of regression coefficients (climate patterns) - and the creation of a new cube of patterns per variable. + """Facilitate the calculation of regression coeffs (climate patterns). + + Also creates of a new cube of patterns per variable. Parameters ---------- - anom_list (cubelist): cube list of variables as anomalies + anom_list : cubelist + cube list of variables as anomalies Returns ------- - regr_var_list (cubelist): cube list of newly created regression slope - value cubes, for each variable - score_list (cubelist): cube list of newly created regression score cubes, - for each variable + regr_var_list : cubelist + cube list of newly created regression slope value cubes, for each var + score_list : cubelist + cube list of newly created regression score cubes, for each var """ regr_var_list = iris.cube.CubeList([]) score_list = iris.cube.CubeList([]) @@ -403,8 +425,10 @@ def write_scores(scores, work_path): Parameters ---------- - scores (cubelist): cube list of regression score cubes, for each variable - work_path (path): path to work_dir, to save scores + scores : cubelist + cube list of regression score cubes, for each variable + work_path : path + path to work_dir, to save scores Returns ------- @@ -427,10 +451,14 @@ def cube_saver(list_of_cubelists, work_path, name_list, mode): Parameters ---------- - list_of_cubelists (list): list containing desired cubelists - work_path (path): path to work_dir, to save cubelists - name_list (list): list of filename strings for saving - mode (str): switch option passed through by ESMValTool config dict + list_of_cubelists : list + list containing desired cubelists + work_path : path + path to work_dir, to save cubelists + name_list : list + list of filename strings for saving + mode : str + switch option passed through by ESMValTool config dict Returns ------- @@ -470,10 +498,14 @@ def saving_outputs( Parameters ---------- - clim_list_final (cubelist): cube list of all variable climatologies - anom_list_final (cubelist): cube list of all variable anomalies - regressions (cubelist): cube list of all variable regression slopes - scores (cubelist): cube list of all variable regression scores + clim_list_final : cubelist + cube list of all variable climatologies + anom_list_final : cubelist + cube list of all variable anomalies + regressions : cubelist + cube list of all variable regression slopes + scores : cubelist + cube list of all variable regression scores Returns ------- @@ -523,7 +555,8 @@ def get_provenance_record(): Returns ------- - record (dict): provenance record + record : dict + provenance record """ record = { "caption": ["Generating Climate Patterns from CMIP6 Models"], @@ -542,7 +575,8 @@ def patterns(model): Parameters ---------- - model (str): model name + model : str + model name Returns ------- @@ -612,7 +646,8 @@ def main(cfg): Parameters ---------- - cfg (dict): the global config dictionary, passed by ESMValTool. + cfg : dict + the global config dictionary, passed by ESMValTool. Returns ------- @@ -623,8 +658,8 @@ def main(cfg): threads = cfg["parallel_threads"] models = [] - for x in input_data: - model = x["dataset"] + for mod in input_data: + model = mod["dataset"] if model not in models: models.append(model) From b9dd91dd430105f4df56aecfe9c7b3b80043309d Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 12:26:05 +0100 Subject: [PATCH 09/70] more docstring fixes --- .../climate_patterns/climate_patterns.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 14c729e9aa..cc32309c67 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -339,6 +339,8 @@ def calculate_regressions(anom_list, yrs=85): ---------- anom_list : cubelist cube list of variables as anomalies + yrs : int + int to specify length of scenario Returns ------- @@ -439,7 +441,7 @@ def write_scores(scores, work_path): mean_score = np.mean(score) # saving scores - file = open(work_path + "scores", "a") + file = open(work_path + "scores", "a", encoding='utf-8') data = "{0:10.3f}".format(mean_score) name = cube.var_name file.write(name + ": " + data + "\n") @@ -506,6 +508,14 @@ def saving_outputs( cube list of all variable regression slopes scores : cubelist cube list of all variable regression scores + imogen_mode : bool + imogen_mode on or off + r2_scores : bool + determinant output on or off + plot_path : str + path to plot_dir + work_path : str + path to work_dir Returns ------- From 3219b2077617fd9f6e246a07eeec768d344ff7e6 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 12:40:27 +0100 Subject: [PATCH 10/70] cp_plotting.py docstring fixes --- .../climate_patterns/cp_plotting.py | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py b/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py index e6e386e487..583d18160e 100644 --- a/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py @@ -13,36 +13,42 @@ def subplot_positions(j): - """Function to manually determine sub-plot positions in a 3x3 figure. + """Determine sub-plot positions in a 3x3 figure. Parameters ---------- - j (int): index of cube position in cubelist + j : int + index of cube position in cubelist Returns ------- - x, y (int): subplot positions + x_pos : int + x subplot position + y_pos : int + y subplot position """ if j <= 2: - y = j - x = 0 + y_pos = j + x_pos = 0 elif 2 < j <= 5: - y = j - 3 - x = 1 + y_pos = j - 3 + x_pos = 1 else: - y = j - 6 - x = 2 + y_pos = j - 6 + x_pos = 2 - return x, y + return x_pos, y_pos def plot_patterns(cube_list, plot_path): - """Plots climate patterns for imogen_mode: off. + """Plot climate patterns for imogen_mode: off. Parameters ---------- - cube_list (cubelist): input cubelist for plotting patterns per variable - plot_path (path): path to plot_dir + cube_list : cubelist + input cubelist for plotting patterns per variable + plot_path : path + path to plot_dir Returns ------- @@ -57,13 +63,14 @@ def plot_patterns(cube_list, plot_path): for j, cube in enumerate(cube_list): # determining plot positions - x, y = subplot_positions(j) + x_pos, y_pos = subplot_positions(j) months = np.arange(1, 13) # plots patterns for an arbitrary grid cell - ax[x, y].plot(months, cube[:, 50, 50].data) - ax[x, y].set_ylabel(str(cube.var_name) + " / " + str(cube.units)) + ax[x_pos, y_pos].plot(months, cube[:, 50, 50].data) + ax[x_pos, + y_pos].set_ylabel(str(cube.var_name) + " / " + str(cube.units)) if j > 5: - ax[x, y].set_xlabel("Time") + ax[x_pos, y_pos].set_xlabel("Time") # January patterns plt.subplot(3, 3, j + 1) @@ -78,12 +85,14 @@ def plot_patterns(cube_list, plot_path): def plot_cp_timeseries(list_cubelists, plot_path): - """Plots timeseries and maps of climatologies, anomalies and patterns. + """Plot timeseries and maps of climatologies, anomalies and patterns. Parameters ---------- - list_cubelists (cubelist): input cubelist for plotting per variable - plot_path (path): path to plot_dir + list_cubelists : cubelist + input cubelist for plotting per variable + plot_path : path + path to plot_dir Returns ------- @@ -150,12 +159,14 @@ def plot_cp_timeseries(list_cubelists, plot_path): def plot_scores(cube_list, plot_path): - """Plots color mesh of scores per variable per month. + """Plot color mesh of scores per variable per month. Parameters ---------- - cube_list (cube): input cubelist for plotting - plot_path (path): path to plot_dir + cube_list : cube + input cubelist for plotting + plot_path : path + path to plot_dir Returns ------- From 151d1abd89a14b9731a922c29d832cb631d0e975 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 12:50:02 +0100 Subject: [PATCH 11/70] rest of docstring fixes --- .../climate_patterns/rename_variables.py | 33 ++++--- .../climate_patterns/sub_functions.py | 85 +++++++++++-------- 2 files changed, 72 insertions(+), 46 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/rename_variables.py b/esmvaltool/diag_scripts/climate_patterns/rename_variables.py index a7ca12c683..4bb06319c6 100644 --- a/esmvaltool/diag_scripts/climate_patterns/rename_variables.py +++ b/esmvaltool/diag_scripts/climate_patterns/rename_variables.py @@ -1,5 +1,4 @@ -"""Script containing cube re-naming functions for main driving scripts, -dependent on recipe switch settings. +"""Script containing cube re-naming functions for driving scripts. Author ------ @@ -12,11 +11,13 @@ def rename_clim_variables(cube): Parameters ---------- - cube (cube): input cube + cube : cube + input cube Returns ------- - cube (cube): cube with renamed variables + cube : cube + cube with renamed variables """ if cube.var_name == "tas": cube.rename("Air Temperature") @@ -56,11 +57,13 @@ def rename_anom_variables(cube): Parameters ---------- - cube (cube): input cube + cube : cube + input cube Returns ------- - cube (cube): cube with renamed variables + cube : cube + cube with renamed variables """ if cube.var_name == "tas": cube.rename("Air Temperature") @@ -100,11 +103,13 @@ def rename_variables(cube): Parameters ---------- - cube (cube): input cube + cube : cube + input cube Returns ------- - cube (cube): cube with renamed variables + cube : cube + cube with renamed variables """ if cube.var_name == "tas": cube.rename("Air Temperature") @@ -141,11 +146,13 @@ def rename_regression_variables(cube): Parameters ---------- - cube (cube): input cube + cube : cube + input cube Returns ------- - cube (cube): cube with renamed variables + cube : cube + cube with renamed variables """ if cube.var_name == "tl1_anom": cube.rename("Air Temperature") @@ -183,11 +190,13 @@ def rename_variables_base(cube): Parameters ---------- - cube (cube): input cube + cube : cube + input cube Returns ------- - cube (cube): cube with renamed variables + cube : cube + cube with renamed variables """ if cube.var_name == "tl1_patt": cube.rename("Air Temperature") diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 17e316dc05..c087801d86 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -1,4 +1,4 @@ -"""Script containing relevant sub-functions for main driving scripts. +"""Script containing relevant sub-functions for driving scripts. Author ------ @@ -6,7 +6,9 @@ """ import logging +import multiprocessing as mp import os +from functools import partial from pathlib import Path import iris @@ -22,11 +24,13 @@ def compute_diagnostic(filename): Parameters ---------- - filename (path): path to load cube file + filename : path + path to load cube file Returns ------- - cube (cube): a cube + cube : cube + a cube """ logger.debug("Loading %s", filename) cube = iris.load_cube(filename) @@ -40,13 +44,17 @@ def area_avg(x, return_cube=None): Parameters ---------- - x (cube): input cube - return_cube (bool): option to return a cube or array + x : cube + input cube + return_cube : bool + option to return a cube or array Returns ------- - x2 (cube): cube with collapsed lat-lons, global mean over time - x2.data (arr): array with collapsed lat-lons, global mean over time + x2 : cube + cube with collapsed lat-lons, global mean over time + x2.data : arr + array with collapsed lat-lons, global mean over time """ if not x.coord("latitude").has_bounds(): x.coord("latitude").guess_bounds() @@ -59,26 +67,32 @@ def area_avg(x, return_cube=None): if return_cube: return x2 - else: - return x2.data + + return x2.data def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): - """Calculate the global mean of a variable in a cube, with options to be - land or ocean weighted. + """Calculate the global mean of a variable in a cube. Parameters ---------- - x (cube): input cube - ocean_frac (cube): ocean fraction cube, found from sftlf - land_frac (cube): land fraction cube, sftlf - land (bool): option to weight be land or ocean - return_cube (bool): option to return a cube or array + x : cube + input cube + ocean_frac : cube + ocean fraction cube, found from sftlf + land_frac : cube + land fraction cube, sftlf + land : bool + option to weight be land or ocean + return_cube : bool + option to return a cube or array Returns ------- - x2 (cube): cube with collapsed lat-lons, global mean over time - x2.data (arr): array with collapsed lat-lons, global mean over time + x2 : cube + cube with collapsed lat-lons, global mean over time + x2.data : arr + array with collapsed lat-lons, global mean over time """ if not x.coord("latitude").has_bounds(): x.coord("latitude").guess_bounds() @@ -116,8 +130,8 @@ def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): if return_cube: return x2 - else: - return x2.data + + return x2.data def make_model_dirs(cube_initial, work_path, plot_path): @@ -125,14 +139,19 @@ def make_model_dirs(cube_initial, work_path, plot_path): Parameters ---------- - cube_initial (cube): initial input cube used to retrieve model name - work_path (path): path to work_dir - plot_path (path): path to plot_dir + cube_initial : cube + initial input cube used to retrieve model name + work_path : path + path to work_dir + plot_path : path + path to plot_dir 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 + model_work_dir : path + path to specific model directory in work_dir + model_plot_dir : path + path to specific plot directory in plot_dir """ w_path = os.path.join(work_path, cube_initial.attributes["source_id"]) p_path = os.path.join(plot_path, cube_initial.attributes["source_id"]) @@ -146,20 +165,20 @@ def make_model_dirs(cube_initial, work_path, plot_path): def parallelise(f, processes=None): - """Wrapper to parallelise any function, graciously supplied by George Ford - (Met Office). + """Wrapper to parallelise any function, by George Ford (Met Office). Parameters ---------- - f (func): function to be parallelised - processes (int): number of threads to be used in parallelisation + f : func + function to be parallelised + processes : int + number of threads to be used in parallelisation Returns ------- - result (any): results of parallelised elements + result : any + results of parallelised elements """ - import multiprocessing as mp - if processes is None: processes = max(1, mp.cpu_count() - 1) if processes <= 0: @@ -172,6 +191,4 @@ def easy_parallise(f, sequence): pool.join() return result - from functools import partial - return partial(easy_parallise, f) From 4449a7dea764ce7013135055f5955a3ee9846af8 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 14:59:33 +0100 Subject: [PATCH 12/70] more Codacy induced fixes --- .../climate_patterns/climate_patterns.py | 2 +- .../climate_patterns/sub_functions.py | 79 ++++++++++--------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index cc32309c67..8fb866ad06 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -442,7 +442,7 @@ def write_scores(scores, work_path): # saving scores file = open(work_path + "scores", "a", encoding='utf-8') - data = "{0:10.3f}".format(mean_score) + data = f"{mean_score:10.3f}" name = cube.var_name file.write(name + ": " + data + "\n") file.close() diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index c087801d86..c6556a52f0 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -39,44 +39,44 @@ def compute_diagnostic(filename): return cube -def area_avg(x, return_cube=None): +def area_avg(cube, return_cube=None): """Calculate the global mean of a variable in a cube, area-weighted. Parameters ---------- - x : cube + cube : cube input cube return_cube : bool option to return a cube or array Returns ------- - x2 : cube + cube2 : cube cube with collapsed lat-lons, global mean over time - x2.data : arr + cube2.data : arr array with collapsed lat-lons, global mean over time """ - if not x.coord("latitude").has_bounds(): - x.coord("latitude").guess_bounds() - if not x.coord("longitude").has_bounds(): - x.coord("longitude").guess_bounds() - area = iris.analysis.cartography.area_weights(x, normalize=False) - x2 = x.collapsed(["latitude", "longitude"], - iris.analysis.MEAN, - weights=area) + if not cube.coord("latitude").has_bounds(): + cube.coord("latitude").guess_bounds() + if not cube.coord("longitude").has_bounds(): + cube.coord("longitude").guess_bounds() + area = iris.analysis.cartography.area_weights(cube, normalize=False) + cube2 = cube.collapsed(["latitude", "longitude"], + iris.analysis.MEAN, + weights=area) if return_cube: - return x2 + return cube2 - return x2.data + return cube2.data -def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): +def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): """Calculate the global mean of a variable in a cube. Parameters ---------- - x : cube + cube : cube input cube ocean_frac : cube ocean fraction cube, found from sftlf @@ -89,17 +89,18 @@ def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): Returns ------- - x2 : cube + cube2 : cube cube with collapsed lat-lons, global mean over time - x2.data : arr + cube2.data : arr array with collapsed lat-lons, global mean over time """ - if not x.coord("latitude").has_bounds(): - x.coord("latitude").guess_bounds() - if not x.coord("longitude").has_bounds(): - x.coord("longitude").guess_bounds() + if not cube.coord("latitude").has_bounds(): + cube.coord("latitude").guess_bounds() + if not cube.coord("longitude").has_bounds(): + cube.coord("longitude").guess_bounds() - global_weights = iris.analysis.cartography.area_weights(x, normalize=False) + global_weights = iris.analysis.cartography.area_weights(cube, + normalize=False) if land is False: ocean_frac.data = np.ma.masked_less(ocean_frac.data, 0.01) @@ -109,11 +110,12 @@ def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): ["latitude", "longitude"], iris.analysis.SUM, weights=weights) / 1e12) print("Ocean area: ", ocean_area.data) - x2 = x.copy() - x2.data = x2.data * global_weights * ocean_frac.data + cube2 = cube.copy() + cube2.data = cube2.data * global_weights * ocean_frac.data - x2 = (x2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / - 1e12 / ocean_area.data) + cube2 = ( + cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / + 1e12 / ocean_area.data) if land: land_frac.data = np.ma.masked_less(land_frac.data, 0.01) @@ -123,15 +125,16 @@ def area_avg_landsea(x, ocean_frac, land_frac, land=True, return_cube=None): ["latitude", "longitude"], iris.analysis.SUM, weights=weights) / 1e12) print("Land area: ", land_area.data) - x2 = x.copy() - x2.data = x2.data * global_weights * land_frac.data - x2 = (x2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / - 1e12 / land_area.data) + cube2 = cube.copy() + cube2.data = cube2.data * global_weights * land_frac.data + cube2 = ( + cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / + 1e12 / land_area.data) if return_cube: - return x2 + return cube2 - return x2.data + return cube2.data def make_model_dirs(cube_initial, work_path, plot_path): @@ -164,8 +167,8 @@ def make_model_dirs(cube_initial, work_path, plot_path): return model_work_dir, model_plot_dir -def parallelise(f, processes=None): - """Wrapper to parallelise any function, by George Ford (Met Office). +def parallelise(func, processes=None): + """Parallelise any function, by George Ford (Met Office). Parameters ---------- @@ -184,11 +187,11 @@ def parallelise(f, processes=None): if processes <= 0: processes = 1 - def easy_parallise(f, sequence): + def easy_parallise(func, sequence): pool = mp.Pool(processes=processes) - result = pool.map_async(f, sequence).get() + result = pool.map_async(func, sequence).get() pool.close() pool.join() return result - return partial(easy_parallise, f) + return partial(easy_parallise, func) From 435409fb2a44b9e5c7b36f2259a23b240118d416 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 1 Sep 2022 15:02:07 +0100 Subject: [PATCH 13/70] more Codacy induced fixes --- .../climate_patterns/cp_plotting.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py b/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py index 583d18160e..ca93276de2 100644 --- a/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py @@ -115,30 +115,32 @@ def plot_cp_timeseries(list_cubelists, plot_path): cube_list = list_cubelists[i] for j, cube in enumerate(cube_list): # determining plot positions - x, y = subplot_positions(j) + x_pos, y_pos = subplot_positions(j) yrs = (1850 + np.arange(cube.shape[0])).astype("float") if i == 0: # climatology avg_cube = sf.area_avg(cube, return_cube=False) - ax1[x, y].plot(yrs, avg_cube) - ax1[x, y].set_ylabel(cube.long_name + " / " + str(cube.units)) + ax1[x_pos, y_pos].plot(yrs, avg_cube) + ax1[x_pos, + y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) if j > 5: - ax1[x, y].set_xlabel("Time") + ax1[x_pos, y_pos].set_xlabel("Time") if i == 1: # anomaly timeseries avg_cube = sf.area_avg(cube, return_cube=False) - ax2[x, y].plot(yrs, avg_cube) - ax2[x, y].set_ylabel(cube.long_name + " / " + str(cube.units)) + ax2[x_pos, y_pos].plot(yrs, avg_cube) + ax2[x_pos, + y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) if j > 5: - ax2[x, y].set_xlabel("Time") + ax2[x_pos, y_pos].set_xlabel("Time") if i == 2: months = np.arange(1, 13) # avg_cube = sf.area_avg(cube, return_cube=False) - ax3[x, y].plot(months, cube[:, 50, 50].data) - ax3[x, y].set_ylabel( + ax3[x_pos, y_pos].plot(months, cube[:, 50, 50].data) + ax3[x_pos, y_pos].set_ylabel( str(cube.var_name) + " / " + str(cube.units)) if j > 5: - ax3[x, y].set_xlabel("Time") + ax3[x_pos, y_pos].set_xlabel("Time") if i == 2: # January patterns plt.subplot(3, 3, j + 1) From 1c7b621e17402bc94fd7fc2c480c794f18edec81 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 2 Sep 2022 10:20:55 +0100 Subject: [PATCH 14/70] more Codacy fixes --- .../climate_patterns/climate_patterns.py | 8 ++++---- .../diag_scripts/climate_patterns/cp_plotting.py | 13 ++++++------- .../diag_scripts/climate_patterns/sub_functions.py | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 8fb866ad06..48d4065433 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -174,8 +174,8 @@ def calculate_diurnal_range(clim_list, ts_list): temp_range_list_ts = iris.cube.CubeList([]) comb_list = [clim_list, ts_list] - for i, _ in enumerate(comb_list): - for cube in comb_list[i]: + for cube_list in comb_list: + for cube in cube_list: if (cube.var_name in ("tasmax", "tasmin")) and cube in clim_list: temp_range_list_clim.append(cube) elif (cube.var_name in ("tasmax", "tasmin")) and cube in ts_list: @@ -625,8 +625,8 @@ def patterns(model): # calculate anomaly over historical + ssp timeseries clim_list_final, anom_list_final = calculate_anomaly(clim_list, ts_list) - for i, _ in enumerate(clim_list_final): - rename_clim_variables(clim_list_final[i]) + for i, cube in enumerate(clim_list_final): + rename_clim_variables(cube) rename_anom_variables(anom_list_final[i]) regressions, scores = calculate_regressions(anom_list_final.copy()) diff --git a/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py b/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py index ca93276de2..7ea93afb81 100644 --- a/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py @@ -54,7 +54,7 @@ def plot_patterns(cube_list, plot_path): ------- None """ - fig, ax = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig, axis = plt.subplots(3, 3, figsize=(14, 12), sharex=True) fig.suptitle("Patterns from a random grid-cell", fontsize=18, y=0.98) plt.figure(figsize=(14, 12)) @@ -66,11 +66,11 @@ def plot_patterns(cube_list, plot_path): x_pos, y_pos = subplot_positions(j) months = np.arange(1, 13) # plots patterns for an arbitrary grid cell - ax[x_pos, y_pos].plot(months, cube[:, 50, 50].data) - ax[x_pos, - y_pos].set_ylabel(str(cube.var_name) + " / " + str(cube.units)) + axis[x_pos, y_pos].plot(months, cube[:, 50, 50].data) + axis[x_pos, + y_pos].set_ylabel(str(cube.var_name) + " / " + str(cube.units)) if j > 5: - ax[x_pos, y_pos].set_xlabel("Time") + axis[x_pos, y_pos].set_xlabel("Time") # January patterns plt.subplot(3, 3, j + 1) @@ -111,8 +111,7 @@ def plot_cp_timeseries(list_cubelists, plot_path): plt.subplots_adjust(hspace=0.5) plt.suptitle("Global Patterns, January", fontsize=18, y=0.95) - for i, _ in enumerate(list_cubelists): - cube_list = list_cubelists[i] + for i, cube_list in enumerate(list_cubelists): for j, cube in enumerate(cube_list): # determining plot positions x_pos, y_pos = subplot_positions(j) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index c6556a52f0..6a3318f775 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -172,7 +172,7 @@ def parallelise(func, processes=None): Parameters ---------- - f : func + func : function function to be parallelised processes : int number of threads to be used in parallelisation From 4f24e6804574f5429fb35193ea68c1f784172400 Mon Sep 17 00:00:00 2001 From: Jon Lillis Date: Thu, 8 Sep 2022 10:54:18 +0100 Subject: [PATCH 15/70] Add `.yml` extension to recipe filename --- .../{recipe_climate_patterns => recipe_climate_patterns.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename esmvaltool/recipes/{recipe_climate_patterns => recipe_climate_patterns.yml} (100%) diff --git a/esmvaltool/recipes/recipe_climate_patterns b/esmvaltool/recipes/recipe_climate_patterns.yml similarity index 100% rename from esmvaltool/recipes/recipe_climate_patterns rename to esmvaltool/recipes/recipe_climate_patterns.yml From 2fe7db1a50ad0e07420d566853ce4dc7cc7c82ab Mon Sep 17 00:00:00 2001 From: Jon Lillis Date: Thu, 8 Sep 2022 14:47:02 +0100 Subject: [PATCH 16/70] Enable metric to run with parallelise flag set to False --- .../climate_patterns/climate_patterns.py | 19 +++++++++---------- .../climate_patterns/sub_functions.py | 5 +++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 48d4065433..a907bcb16b 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -580,7 +580,7 @@ def get_provenance_record(): return record -def patterns(model): +def patterns(model, cfg): """Driving function for script, taking in model data and saving parameters. Parameters @@ -592,13 +592,12 @@ def patterns(model): ------- None """ - with run_diagnostic() as cfg: - input_data = cfg["input_data"].values() - grid_spec = cfg["grid"] - imogen_mode = cfg["imogen_mode"] - r2_scores = cfg["output_r2_scores"] - work_path = cfg["work_dir"] + "/" - plot_path = cfg["plot_dir"] + "/" + input_data = cfg["input_data"].values() + grid_spec = cfg["grid"] + imogen_mode = cfg["imogen_mode"] + r2_scores = cfg["output_r2_scores"] + work_path = cfg["work_dir"] + "/" + plot_path = cfg["plot_dir"] + "/" clim_list = iris.cube.CubeList([]) ts_list = iris.cube.CubeList([]) @@ -674,10 +673,10 @@ def main(cfg): models.append(model) if parallelise is True: - sf.parallelise(patterns, threads)(models) + sf.parallelise(patterns, threads)(models, cfg) else: for model in models: - patterns(model) + patterns(model, cfg) if __name__ == "__main__": diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 6a3318f775..ebf4b84d40 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -187,9 +187,10 @@ def parallelise(func, processes=None): if processes <= 0: processes = 1 - def easy_parallise(func, sequence): + def easy_parallise(f, sequence, cfg): pool = mp.Pool(processes=processes) - result = pool.map_async(func, sequence).get() + config_wrapper=partial(f, cfg=cfg) + result = pool.map_async(config_wrapper, sequence).get() pool.close() pool.join() return result From b5012868cff60028f1ce82af0ad627a6c914df01 Mon Sep 17 00:00:00 2001 From: Jon Lillis Date: Thu, 8 Sep 2022 14:58:02 +0100 Subject: [PATCH 17/70] Add maintainer to recipe file --- esmvaltool/recipes/recipe_climate_patterns.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index cbca21a02f..5ff0f2ab64 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -8,6 +8,9 @@ documentation: authors: - munday_gregory + maintainer: + - munday_gregory + preprocessors: global_mean_monthly: monthly_statistics: From c765763f7e811562b7a301e5c9c075cf61deac3a Mon Sep 17 00:00:00 2001 From: Jon Lillis Date: Thu, 8 Sep 2022 15:00:42 +0100 Subject: [PATCH 18/70] Correct use of whitespace --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index ebf4b84d40..2cff1769cf 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -189,7 +189,7 @@ def parallelise(func, processes=None): def easy_parallise(f, sequence, cfg): pool = mp.Pool(processes=processes) - config_wrapper=partial(f, cfg=cfg) + config_wrapper = partial(f, cfg=cfg) result = pool.map_async(config_wrapper, sequence).get() pool.close() pool.join() From eccc4928e1f879e5c043e8aee1025c40412d7a27 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 7 Oct 2022 11:10:57 +0100 Subject: [PATCH 19/70] Some fixes/improvements, and recipe fix --- .../diag_scripts/climate_patterns/climate_patterns.py | 10 +++++----- .../diag_scripts/climate_patterns/sub_functions.py | 4 ++-- ...pe_climate_patterns => recipe_climate_patterns.yml} | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) rename esmvaltool/recipes/{recipe_climate_patterns => recipe_climate_patterns.yml} (98%) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 48d4065433..2932817e7a 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -323,9 +323,9 @@ def regression_units(tas, cube): units : str string of calculated regression units """ - print("Cube Units: ", cube.units) + logger.debug("Cube Units: ", cube.units) units = cube.units / tas.units - print("Regression Units: ", units) + logger.debug("Regression Units: ", units) return units @@ -486,7 +486,7 @@ def cube_saver(list_of_cubelists, work_path, name_list, mode): iris.save(list_of_cubelists[2], work_path + name_list[2]) -def saving_outputs( +def save_outputs( clim_list_final, anom_list_final, regressions, @@ -608,7 +608,7 @@ def patterns(model): input_file = dataset["filename"] # preparing single cube - cube_initial = sf.compute_diagnostic(input_file) + cube_initial = sf.load_cube(input_file) if grid_spec == "constrained": cube = constrain_latitude(cube_initial) @@ -634,7 +634,7 @@ def patterns(model): model_work_dir, model_plot_dir = sf.make_model_dirs( cube_initial, work_path, plot_path) - saving_outputs( + save_outputs( clim_list_final, anom_list_final, regressions, diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 6a3318f775..e93d80bcd9 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -19,7 +19,7 @@ logger = logging.getLogger(Path(__file__).stem) -def compute_diagnostic(filename): +def load_cube(filename): """Load cube, remove any dimensions of length: 1. Parameters @@ -168,7 +168,7 @@ def make_model_dirs(cube_initial, work_path, plot_path): def parallelise(func, processes=None): - """Parallelise any function, by George Ford (Met Office). + """Parallelise any function, by George Ford, Met Office. Parameters ---------- diff --git a/esmvaltool/recipes/recipe_climate_patterns b/esmvaltool/recipes/recipe_climate_patterns.yml similarity index 98% rename from esmvaltool/recipes/recipe_climate_patterns rename to esmvaltool/recipes/recipe_climate_patterns.yml index cbca21a02f..e2b42e9743 100644 --- a/esmvaltool/recipes/recipe_climate_patterns +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -28,7 +28,7 @@ monthly_global_settings_day: &monthly_global_settings_day project: CMIP6 preprocessor: global_mean_monthly -CMIP6_DAY: &cmip6_no_tasmax +CMIP6_no_tasmax: &cmip6_no_tasmax - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r2i1p1f1, grid: gr, start_year: 1850, end_year: 2099} - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: NorESM2-MM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} @@ -160,12 +160,12 @@ diagnostics: <<: *monthly_global_settings additional_datasets: *cmip6_no_tasmax - rsds_585: + rsds_585_no_tasmax: short_name: rsds <<: *monthly_global_settings additional_datasets: *cmip6_no_tasmax - rlds_585: + rlds_585_no_tasmax: short_name: rlds <<: *monthly_global_settings additional_datasets: *cmip6_no_tasmax From 7b9376d56c9cf13dd03b014db3d64a3611aeeb43 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 7 Jul 2023 11:56:23 +0100 Subject: [PATCH 20/70] Added models, added land warming switch --- .../climate_patterns/climate_patterns.py | 87 +++++++----- .../climate_patterns/sub_functions.py | 72 +++++++--- .../recipes/recipe_climate_patterns.yml | 128 +++++++++++++++++- 3 files changed, 228 insertions(+), 59 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 676dbeff6c..a823d9693c 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -77,7 +77,8 @@ def climatology(cube, syr=1850, eyr=1889): iris.Constraint( time=lambda t: syr <= t.point.year <= eyr, month_number=lambda t: 1 <= t.point <= 12, - )) + ) + ) cube_aggregated = make_monthly_climatology(cube_40yr) return cube_aggregated @@ -101,7 +102,8 @@ def constrain_latitude(cube, min_lat=-55, max_lat=82.5): cube with latitudes set from -55 to 82.5 degrees """ cube_clipped = cube.extract( - iris.Constraint(latitude=lambda cell: max_lat >= cell >= min_lat)) + iris.Constraint(latitude=lambda cell: max_lat >= cell >= min_lat) + ) return cube_clipped @@ -120,10 +122,8 @@ def make_monthly_climatology(cube): cube aggregated by month_number """ if not cube.coords("month_number"): - iris.coord_categorisation.add_month_number(cube, "time", - "month_number") - cube_month_climatol = cube.aggregated_by("month_number", - iris.analysis.MEAN) + iris.coord_categorisation.add_month_number(cube, "time", "month_number") + cube_month_climatol = cube.aggregated_by("month_number", iris.analysis.MEAN) return cube_month_climatol @@ -188,13 +188,13 @@ def calculate_diurnal_range(clim_list, ts_list): # append diurnal range to lists clim_list_final, ts_list_final = append_diurnal_range( - derived_diurnal_clim, derived_diurnal_ts, clim_list, ts_list) + derived_diurnal_clim, derived_diurnal_ts, clim_list, ts_list + ) return clim_list_final, ts_list_final -def append_diurnal_range(derived_diurnal_clim, derived_diurnal_ts, clim_list, - ts_list): +def append_diurnal_range(derived_diurnal_clim, derived_diurnal_ts, clim_list, ts_list): """Append diurnal range to cubelists. Parameters @@ -252,22 +252,22 @@ def calculate_anomaly(clim_list, ts_list): cubelist of anomaly vars, inc. diurnal range """ # calculate diurnal temperature range cube - clim_list_final, ts_list_final = calculate_diurnal_range( - clim_list, ts_list) + clim_list_final, ts_list_final = calculate_diurnal_range(clim_list, ts_list) anom_list_final = ts_list_final.copy() # calc the anom by subtracting the monthly climatology from # the time series for i, _ in enumerate(ts_list_final): - i_months = (anom_list_final[i].coord("month_number").points - 1 - ) # -1 because months are numbered 1..12 + i_months = ( + anom_list_final[i].coord("month_number").points - 1 + ) # -1 because months are numbered 1..12 anom_list_final[i].data -= clim_list_final[i][i_months].data return clim_list_final, anom_list_final -def regression(tas, cube_data): +def regression(tas, cube_data, ocean_frac, land_frac, area="global"): """Calculate coeffs of regression between global surf temp and variable. Parameters @@ -289,14 +289,21 @@ def regression(tas, cube_data): slope_array = np.full(tas.data.shape[1:], np.nan) score_array = np.full(tas.data.shape[1:], np.nan) - # calculate global average - tas_data = sf.area_avg(tas, return_cube=False) + if area == "land": + # calculate average warming over land + tas_data = sf.area_avg_landsea( + tas, ocean_frac, land_frac, land=True, return_cube=False + ) + else: + # calculate global average warming + tas_data = sf.area_avg(tas, return_cube=False) for i in range(tas.data.shape[1]): for j in range(tas.data.shape[2]): if tas.data[0, i, j] is not np.ma.masked: model = sklearn.linear_model.LinearRegression( - fit_intercept=False, copy_X=True) + fit_intercept=False, copy_X=True + ) x_val = tas_data.reshape(-1, 1) y_val = cube_data[:, i, j] @@ -330,7 +337,7 @@ def regression_units(tas, cube): return units -def calculate_regressions(anom_list, yrs=85): +def calculate_regressions(anom_list, ocean_frac, land_frac, area, yrs=85): """Facilitate the calculation of regression coeffs (climate patterns). Also creates of a new cube of patterns per variable. @@ -355,7 +362,6 @@ def calculate_regressions(anom_list, yrs=85): for cube in anom_list: if cube.var_name == "tl1_anom": - # convert years to months when selecting tas = cube[-months:] @@ -370,8 +376,9 @@ def calculate_regressions(anom_list, yrs=85): month_cube_ssp = cube_ssp.extract(month_constraint) month_tas = tas.extract(month_constraint) - regr_array, score_array = regression(month_tas, - month_cube_ssp.data) + regr_array, score_array = regression( + month_tas, month_cube_ssp.data, ocean_frac, land_frac, area=area + ) # re-creating cube if cube.var_name in ("swdown_anom", "lwdown_anom"): @@ -441,7 +448,7 @@ def write_scores(scores, work_path): mean_score = np.mean(score) # saving scores - file = open(work_path + "scores", "a", encoding='utf-8') + file = open(work_path + "scores", "a", encoding="utf-8") data = f"{mean_score:10.3f}" name = cube.var_name file.write(name + ": " + data + "\n") @@ -535,10 +542,7 @@ def save_outputs( plot_scores(list_of_cubelists[3], plot_path) write_scores(scores, work_path) plot_cp_timeseries(list_of_cubelists, plot_path) - cube_saver(list_of_cubelists, - work_path, - name_list, - mode="imogen_scores") + cube_saver(list_of_cubelists, work_path, name_list, mode="imogen_scores") else: plot_cp_timeseries(list_of_cubelists, plot_path) @@ -598,6 +602,7 @@ def patterns(model, cfg): r2_scores = cfg["output_r2_scores"] work_path = cfg["work_dir"] + "/" plot_path = cfg["plot_dir"] + "/" + area = cfg["area"] clim_list = iris.cube.CubeList([]) ts_list = iris.cube.CubeList([]) @@ -614,12 +619,24 @@ def patterns(model, cfg): else: cube = cube_initial - # appending to timeseries list - ts_list.append(cube) + if dataset["exp"] not in ["historical-ssp585", "ssp585"]: + sftlf = cube + elif dataset["exp"] == "ssp585": + # appending to timeseries list + ts_list.append(cube) + # use first year as baseline for anomaly + clim_cube = cube[0] + clim_list.append(clim_cube) + else: + # appending to timeseries list + ts_list.append(cube) + + # making climatology + clim_cube = climatology(cube) + clim_list.append(clim_cube) - # making climatology - clim_cube = climatology(cube) - clim_list.append(clim_cube) + # calculate land/ocean_fracs + ocean_frac, land_frac = sf.ocean_fraction_calc(sftlf) # calculate anomaly over historical + ssp timeseries clim_list_final, anom_list_final = calculate_anomaly(clim_list, ts_list) @@ -628,10 +645,13 @@ def patterns(model, cfg): rename_clim_variables(cube) rename_anom_variables(anom_list_final[i]) - regressions, scores = calculate_regressions(anom_list_final.copy()) + regressions, scores = calculate_regressions( + anom_list_final.copy(), ocean_frac, land_frac, area + ) model_work_dir, model_plot_dir = sf.make_model_dirs( - cube_initial, work_path, plot_path) + cube_initial, work_path, plot_path + ) save_outputs( clim_list_final, @@ -680,6 +700,5 @@ def main(cfg): if __name__ == "__main__": - with run_diagnostic() as config: main(config) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index ae7e06291e..fe417474ab 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -61,9 +61,7 @@ def area_avg(cube, return_cube=None): if not cube.coord("longitude").has_bounds(): cube.coord("longitude").guess_bounds() area = iris.analysis.cartography.area_weights(cube, normalize=False) - cube2 = cube.collapsed(["latitude", "longitude"], - iris.analysis.MEAN, - weights=area) + cube2 = cube.collapsed(["latitude", "longitude"], iris.analysis.MEAN, weights=area) if return_cube: return cube2 @@ -71,6 +69,35 @@ def area_avg(cube, return_cube=None): return cube2.data +def ocean_fraction_calc(sftlf): + """Calculates land and ocean fractions (gridded) and ocean fraction (float) + for area-weights and EBM. + + Parameters + ---------- + sftlf: cube + land-fraction cube from piControl experiment + + Returns + ------- + ocean_frac: cube + ocean_fraction cube for area-weights + land_frac: cube + land_fraction cube for area-weights + of: float + ocean fraction float for EBM calculations + """ + sftlf.coord("latitude").coord_system = iris.coord_systems.GeogCS(6371229.0) + sftlf.coord("longitude").coord_system = iris.coord_systems.GeogCS(6371229.0) + sftof = sftlf.copy() + sftof.data = 100.0 - sftlf.data + + ocean_frac = sftof / 100 + land_frac = sftlf / 100 + + return ocean_frac, land_frac + + def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): """Calculate the global mean of a variable in a cube. @@ -99,37 +126,44 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): if not cube.coord("longitude").has_bounds(): cube.coord("longitude").guess_bounds() - global_weights = iris.analysis.cartography.area_weights(cube, - normalize=False) + global_weights = iris.analysis.cartography.area_weights(cube, normalize=False) if land is False: ocean_frac.data = np.ma.masked_less(ocean_frac.data, 0.01) - weights = iris.analysis.cartography.area_weights(ocean_frac, - normalize=False) - ocean_area = (ocean_frac.collapsed( - ["latitude", "longitude"], iris.analysis.SUM, weights=weights) / - 1e12) + weights = iris.analysis.cartography.area_weights(ocean_frac, normalize=False) + ocean_area = ( + ocean_frac.collapsed( + ["latitude", "longitude"], iris.analysis.SUM, weights=weights + ) + / 1e12 + ) print("Ocean area: ", ocean_area.data) cube2 = cube.copy() cube2.data = cube2.data * global_weights * ocean_frac.data cube2 = ( - cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / - 1e12 / ocean_area.data) + cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) + / 1e12 + / ocean_area.data + ) if land: land_frac.data = np.ma.masked_less(land_frac.data, 0.01) - weights = iris.analysis.cartography.area_weights(land_frac, - normalize=False) - land_area = (land_frac.collapsed( - ["latitude", "longitude"], iris.analysis.SUM, weights=weights) / - 1e12) + weights = iris.analysis.cartography.area_weights(land_frac, normalize=False) + land_area = ( + land_frac.collapsed( + ["latitude", "longitude"], iris.analysis.SUM, weights=weights + ) + / 1e12 + ) print("Land area: ", land_area.data) cube2 = cube.copy() cube2.data = cube2.data * global_weights * land_frac.data cube2 = ( - cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / - 1e12 / land_area.data) + cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) + / 1e12 + / land_area.data + ) if return_cube: return cube2 diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index 1916879f49..29057d0f3b 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -21,6 +21,12 @@ preprocessors: start_latitude: -55, end_latitude: 82.5, step_latitude: 2.5} scheme: linear + downscale_sftlf: + regrid: + target_grid: {start_longitude: -180, end_longitude: 176.25, step_longitude: 3.75, + start_latitude: -55, end_latitude: 82.5, step_latitude: 2.5} + scheme: linear + monthly_global_settings: &monthly_global_settings mip: Amon project: CMIP6 @@ -31,14 +37,65 @@ monthly_global_settings_day: &monthly_global_settings_day project: CMIP6 preprocessor: global_mean_monthly + +CMIP6_landfrac: &cmip6_landfrac + - {dataset: ACCESS-CM2, exp: piControl, ensemble: r1i1p1f1, grid: gn, institute: CSIRO-ARCCSS} + - {dataset: ACCESS-ESM1-5, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: AWI-CM-1-1-MR, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: BCC-CSM2-MR, exp: hist-resIPO,ensemble: r1i1p1f1, grid: gn} + - {dataset: CanESM5, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: CanESM5-CanOE, exp: piControl, ensemble: r1i1p2f1, grid: gn} + - {dataset: CanESM5-1, exp: piControl, ensemble: r1i1p1f1, grid: gn} + # - {dataset: CAS-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use + - {dataset: CESM2, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} + - {dataset: CESM2-WACCM, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} + - {dataset: CMCC-ESM2, exp: piControl, ensemble: r1i1p1f1, grid: gn} + # - {dataset: CMCC-CM2-SR5, exp: piControl, ensemble: r1i1p1f1, grid: gn} # No tasmin/tasmax + - {dataset: CNRM-CM6-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} + - {dataset: CNRM-CM6-1-HR, exp: piControl, ensemble: r1i1p1f2, grid: gr} + - {dataset: CNRM-ESM2-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} + # - {dataset: E3SM-1-0, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Tasmax == tasmin + - {dataset: EC-Earth3, exp: piControl, ensemble: r1i1p1f1, grid: gr} + # - {dataset: EC-Earth3-CC, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use + - {dataset: EC-Earth3-Veg, exp: piControl, ensemble: r1i1p1f1, grid: gr} + # - {dataset: FGOALS-f3-L, exp: historical, ensemble: r1i1p1f1, grid: gr} # No tasmin/tasmax + - {dataset: FGOALS-g3, exp: piControl, ensemble: r1i1p1f1, grid: gn} + # - {dataset: FIO-ESM-2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use + - {dataset: GFDL-CM4, exp: piControl, ensemble: r1i1p1f1, grid: gr1} + - {dataset: GFDL-ESM4, exp: ssp370, ensemble: r1i1p1f1, grid: gr1} + - {dataset: GISS-E2-1-H, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: GISS-E2-1-G, exp: piControl, ensemble: r1i1p5f1, grid: gn} + - {dataset: GISS-E2-2-G, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: HadGEM3-GC31-LL, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: HadGEM3-GC31-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: INM-CM4-8, exp: piControl, ensemble: r1i1p1f1, grid: gr1} + - {dataset: INM-CM5-0, exp: abrupt-4xCO2, ensemble: r1i1p1f1, grid: gr1} + - {dataset: IPSL-CM6A-LR, exp: piControl, ensemble: r1i1p1f1, grid: gr} + # - {dataset: KACE-1-0-G, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use, weird tasmin/tasmax + # - {dataset: KIOST-ESM, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use + - {dataset: MIROC6, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: MIROC-ES2L, exp: piControl, ensemble: r1i1p1f2, grid: gn} + - {dataset: MIROC-ES2H, exp: piControl, ensemble: r1i1p4f2, grid: gn} + - {dataset: MPI-ESM1-2-HR, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: MPI-ESM1-2-LR, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: MRI-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: NorESM2-LM, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: NorESM2-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: TaiESM1, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: UKESM1-0-LL, exp: piControl, ensemble: r1i1p1f2, grid: gn} + +CMIP6_no_hist_tasmax: &cmip6_no_hist_tasmax + - {dataset: CESM2, exp: ssp585, ensemble: r4i1p1f1, grid: gn, start_year: 2015, end_year: 2100} + - {dataset: CESM2-WACCM, exp: ssp585, ensemble: r4i1p1f1, grid: gn, start_year: 2015, end_year: 2100} + CMIP6_no_tasmax: &cmip6_no_tasmax - - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r2i1p1f1, grid: gr, start_year: 1850, end_year: 2099} + # - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2099} - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: NorESM2-MM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: TaiESM1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} CMIP6_DAY: &cmip6_day - - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r2i1p1f1, grid: gr, start_year: 1850, end_year: 2099} + # - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2099} - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: NorESM2-MM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: TaiESM1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} @@ -49,17 +106,20 @@ CMIP6_FULL: &cmip6_full - {dataset: AWI-CM-1-1-MR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: BCC-CSM2-MR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: CanESM5, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: CanESM5-1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: CanESM5-CanOE, exp: [historical, ssp585], ensemble: r1i1p2f1, grid: gn, start_year: 1850, end_year: 2100} - - {dataset: CAS-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: CAS-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: CMCC-ESM2, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: CMCC-CM2-SR5, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: CNRM-CM6-1, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} - {dataset: CNRM-CM6-1-HR, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} - {dataset: CNRM-ESM2-1, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} - {dataset: EC-Earth3, exp: [historical, ssp585], ensemble: r11i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - - {dataset: EC-Earth3-CC, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + # - {dataset: EC-Earth3-CC, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - {dataset: EC-Earth3-Veg, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + # - {dataset: FGOALS-f3-L, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - {dataset: FGOALS-g3, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - - {dataset: FIO-ESM-2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: FIO-ESM-2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: GFDL-CM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: GFDL-ESM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: GISS-E2-1-H, exp: [historical, ssp585], ensemble: r3i1p1f2, grid: gn, start_year: 1850, end_year: 2100} @@ -70,9 +130,11 @@ CMIP6_FULL: &cmip6_full - {dataset: INM-CM4-8, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: INM-CM5-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: IPSL-CM6A-LR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - - {dataset: KACE-1-0-G, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + # - {dataset: KACE-1-0-G, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + # - {dataset: KIOST-ESM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - {dataset: MIROC6, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MIROC-ES2L, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: MIROC-ES2H, exp: [historical, ssp585], ensemble: r1i1p4f2, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MPI-ESM1-2-HR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MPI-ESM1-2-LR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MRI-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} @@ -83,6 +145,14 @@ diagnostics: description: Mean monthly variables variables: + + sftlf: + short_name: sftlf + mip: fx + project: CMIP6 + preprocessor: downscale_sftlf + additional_datasets: *cmip6_landfrac + tasmax_585: short_name: tasmax <<: *monthly_global_settings @@ -173,6 +243,51 @@ diagnostics: <<: *monthly_global_settings additional_datasets: *cmip6_no_tasmax + tasmax_no_hist_tasmax: + short_name: tasmax + <<: *monthly_global_settings + additional_datasets: *cmip6_no_hist_tasmax + + tasmin_no_hist_tasmax: + short_name: tasmin + <<: *monthly_global_settings + additional_datasets: *cmip6_no_hist_tasmax + + tas_no_hist_tasmax: + short_name: tas + <<: *monthly_global_settings + additional_datasets: *cmip6_no_hist_tasmax + + huss_no_hist_tasmax: + short_name: huss + <<: *monthly_global_settings + additional_datasets: *cmip6_no_hist_tasmax + + pr_no_hist_tasmax: + short_name: pr + <<: *monthly_global_settings + additional_datasets: *cmip6_no_hist_tasmax + + sfcWind_no_hist_tasmax: + short_name: sfcWind + <<: *monthly_global_settings + additional_datasets: *cmip6_no_hist_tasmax + + ps_no_hist_tasmax: + short_name: ps + <<: *monthly_global_settings + additional_datasets: *cmip6_no_hist_tasmax + + rsds_no_hist_tasmax: + short_name: rsds + <<: *monthly_global_settings + additional_datasets: *cmip6_no_hist_tasmax + + rlds_no_hist_tasmax: + short_name: rlds + <<: *monthly_global_settings + additional_datasets: *cmip6_no_hist_tasmax + scripts: climate_patterns_script: script: climate_patterns/climate_patterns.py @@ -181,3 +296,4 @@ diagnostics: output_r2_scores: on # options: on, off parallelise: on # options: on, off parallel_threads: 40 # int, optional + area: land # options global, land From 1da7debe21048b75c99908dff9a21ad88ec6730d Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 7 Jul 2023 12:08:23 +0100 Subject: [PATCH 21/70] Fixed flake8 issues --- .../climate_patterns/climate_patterns.py | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index a823d9693c..ed62bfe5be 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -122,8 +122,15 @@ def make_monthly_climatology(cube): cube aggregated by month_number """ if not cube.coords("month_number"): - iris.coord_categorisation.add_month_number(cube, "time", "month_number") - cube_month_climatol = cube.aggregated_by("month_number", iris.analysis.MEAN) + iris.coord_categorisation.add_month_number( + cube, + "time", + "month_number" + ) + cube_month_climatol = cube.aggregated_by( + "month_number", + iris.analysis.MEAN + ) return cube_month_climatol @@ -194,7 +201,10 @@ def calculate_diurnal_range(clim_list, ts_list): return clim_list_final, ts_list_final -def append_diurnal_range(derived_diurnal_clim, derived_diurnal_ts, clim_list, ts_list): +def append_diurnal_range(derived_diurnal_clim, + derived_diurnal_ts, + clim_list, + ts_list): """Append diurnal range to cubelists. Parameters @@ -252,7 +262,10 @@ def calculate_anomaly(clim_list, ts_list): cubelist of anomaly vars, inc. diurnal range """ # calculate diurnal temperature range cube - clim_list_final, ts_list_final = calculate_diurnal_range(clim_list, ts_list) + clim_list_final, ts_list_final = calculate_diurnal_range( + clim_list, + ts_list + ) anom_list_final = ts_list_final.copy() @@ -377,7 +390,11 @@ def calculate_regressions(anom_list, ocean_frac, land_frac, area, yrs=85): month_tas = tas.extract(month_constraint) regr_array, score_array = regression( - month_tas, month_cube_ssp.data, ocean_frac, land_frac, area=area + month_tas, + month_cube_ssp.data, + ocean_frac, + land_frac, + area=area ) # re-creating cube @@ -542,7 +559,12 @@ def save_outputs( plot_scores(list_of_cubelists[3], plot_path) write_scores(scores, work_path) plot_cp_timeseries(list_of_cubelists, plot_path) - cube_saver(list_of_cubelists, work_path, name_list, mode="imogen_scores") + cube_saver( + list_of_cubelists, + work_path, + name_list, + mode="imogen_scores" + ) else: plot_cp_timeseries(list_of_cubelists, plot_path) From db241f1c9d1b4ad4f97a158d93a5ebdd3ca144cc Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 7 Jul 2023 12:16:13 +0100 Subject: [PATCH 22/70] Fixed more flake8 issues --- .../climate_patterns/sub_functions.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index fe417474ab..caea3e4515 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -61,7 +61,11 @@ def area_avg(cube, return_cube=None): if not cube.coord("longitude").has_bounds(): cube.coord("longitude").guess_bounds() area = iris.analysis.cartography.area_weights(cube, normalize=False) - cube2 = cube.collapsed(["latitude", "longitude"], iris.analysis.MEAN, weights=area) + cube2 = cube.collapsed( + ["latitude", "longitude"], + iris.analysis.MEAN, + weights=area + ) if return_cube: return cube2 @@ -87,8 +91,12 @@ def ocean_fraction_calc(sftlf): of: float ocean fraction float for EBM calculations """ - sftlf.coord("latitude").coord_system = iris.coord_systems.GeogCS(6371229.0) - sftlf.coord("longitude").coord_system = iris.coord_systems.GeogCS(6371229.0) + sftlf.coord("latitude").coord_system = iris.coord_systems.GeogCS( + 6371229.0 + ) + sftlf.coord("longitude").coord_system = iris.coord_systems.GeogCS( + 6371229.0 + ) sftof = sftlf.copy() sftof.data = 100.0 - sftlf.data @@ -126,11 +134,17 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): if not cube.coord("longitude").has_bounds(): cube.coord("longitude").guess_bounds() - global_weights = iris.analysis.cartography.area_weights(cube, normalize=False) + global_weights = iris.analysis.cartography.area_weights( + cube, + normalize=False + ) if land is False: ocean_frac.data = np.ma.masked_less(ocean_frac.data, 0.01) - weights = iris.analysis.cartography.area_weights(ocean_frac, normalize=False) + weights = iris.analysis.cartography.area_weights( + ocean_frac, + normalize=False + ) ocean_area = ( ocean_frac.collapsed( ["latitude", "longitude"], iris.analysis.SUM, weights=weights @@ -149,7 +163,10 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): if land: land_frac.data = np.ma.masked_less(land_frac.data, 0.01) - weights = iris.analysis.cartography.area_weights(land_frac, normalize=False) + weights = iris.analysis.cartography.area_weights( + land_frac, + normalize=False + ) land_area = ( land_frac.collapsed( ["latitude", "longitude"], iris.analysis.SUM, weights=weights From dd2b03cff1b88d325d023f2a0216ab0dcdbdc197 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 7 Jul 2023 15:13:38 +0100 Subject: [PATCH 23/70] More flake8 fixes --- .../climate_patterns/climate_patterns.py | 25 +++++++++++++------ .../climate_patterns/sub_functions.py | 3 +-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index ed62bfe5be..eac988b2d1 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -289,6 +289,12 @@ def regression(tas, cube_data, ocean_frac, land_frac, area="global"): near-surface air temperature cube_data : arr cube.data array of a variable + ocean_frac: cube + gridded ocean fraction + land_frac: cube + gridded land fraction + area: str + area over which to calculate patterns Returns ------- @@ -343,9 +349,7 @@ def regression_units(tas, cube): units : str string of calculated regression units """ - logger.debug("Cube Units: ", cube.units) units = cube.units / tas.units - logger.debug("Regression Units: ", units) return units @@ -359,6 +363,12 @@ def calculate_regressions(anom_list, ocean_frac, land_frac, area, yrs=85): ---------- anom_list : cubelist cube list of variables as anomalies + ocean_frac: cube + gridded ocean fraction + land_frac: cube + gridded land fraction + area: str + area over which to calculate patterns yrs : int int to specify length of scenario @@ -463,13 +473,12 @@ def write_scores(scores, work_path): for cube in scores: score = sf.area_avg(cube, return_cube=False) mean_score = np.mean(score) - - # saving scores - file = open(work_path + "scores", "a", encoding="utf-8") data = f"{mean_score:10.3f}" name = cube.var_name - file.write(name + ": " + data + "\n") - file.close() + # saving scores + with open(work_path + "scores", "a") as f: + f.write(name + ": " + data + "\n") + f.close() def cube_saver(list_of_cubelists, work_path, name_list, mode): @@ -613,6 +622,8 @@ def patterns(model, cfg): ---------- model : str model name + cfg: dict + Dictionary passed in by ESMValTool preprocessors Returns ------- diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index caea3e4515..296bd72c12 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -74,8 +74,7 @@ def area_avg(cube, return_cube=None): def ocean_fraction_calc(sftlf): - """Calculates land and ocean fractions (gridded) and ocean fraction (float) - for area-weights and EBM. + """Calculates gridded land and ocean fractions. Parameters ---------- From 983a7adb58f8286e84a6a53da40afc26e3faa5f4 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Mon, 10 Jul 2023 10:04:53 +0100 Subject: [PATCH 24/70] More codacy fixes --- .../diag_scripts/climate_patterns/climate_patterns.py | 8 ++++---- .../climate_patterns/{cp_plotting.py => plotting.py} | 0 esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) rename esmvaltool/diag_scripts/climate_patterns/{cp_plotting.py => plotting.py} (100%) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index eac988b2d1..7731d44288 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -43,7 +43,7 @@ import numpy as np import sklearn.linear_model import sub_functions as sf -from cp_plotting import plot_cp_timeseries, plot_patterns, plot_scores +from plotting import plot_cp_timeseries, plot_patterns, plot_scores from rename_variables import ( rename_anom_variables, rename_clim_variables, @@ -476,9 +476,9 @@ def write_scores(scores, work_path): data = f"{mean_score:10.3f}" name = cube.var_name # saving scores - with open(work_path + "scores", "a") as f: - f.write(name + ": " + data + "\n") - f.close() + with open(work_path + "scores", "a", encoding='utf-8') as file: + file.write(name + ": " + data + "\n") + file.close() def cube_saver(list_of_cubelists, work_path, name_list, mode): diff --git a/esmvaltool/diag_scripts/climate_patterns/cp_plotting.py b/esmvaltool/diag_scripts/climate_patterns/plotting.py similarity index 100% rename from esmvaltool/diag_scripts/climate_patterns/cp_plotting.py rename to esmvaltool/diag_scripts/climate_patterns/plotting.py diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 296bd72c12..b75f683f0c 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -217,7 +217,7 @@ def make_model_dirs(cube_initial, work_path, plot_path): return model_work_dir, model_plot_dir -def parallelise(func, processes=None): +def parallelise(function, processes=None): """Parallelise any function, by George Ford, Met Office. Parameters @@ -237,12 +237,12 @@ def parallelise(func, processes=None): if processes <= 0: processes = 1 - def easy_parallise(f, sequence, cfg): + def easy_parallise(func, sequence, cfg): pool = mp.Pool(processes=processes) - config_wrapper = partial(f, cfg=cfg) + config_wrapper = partial(func, cfg=cfg) result = pool.map_async(config_wrapper, sequence).get() pool.close() pool.join() return result - return partial(easy_parallise, func) + return partial(easy_parallise, function) From a7a31732e867fc05bc23386272d3f261306fee12 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Mon, 10 Jul 2023 10:32:08 +0100 Subject: [PATCH 25/70] Style fix --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index b75f683f0c..af2ce1bf18 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -74,7 +74,7 @@ def area_avg(cube, return_cube=None): def ocean_fraction_calc(sftlf): - """Calculates gridded land and ocean fractions. + """Calculate gridded land and ocean fractions. Parameters ---------- @@ -222,7 +222,7 @@ def parallelise(function, processes=None): Parameters ---------- - func : function + function : function function to be parallelised processes : int number of threads to be used in parallelisation From d7190b975cce76953d8109280a72aa1e299f5b15 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 8 Feb 2024 15:11:00 +0000 Subject: [PATCH 26/70] Refactoring --- .../climate_patterns/climate_patterns.py | 240 ++++++++++-------- .../climate_patterns/sub_functions.py | 28 +- .../recipes/recipe_climate_patterns.yml | 181 +++++-------- 3 files changed, 219 insertions(+), 230 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 7731d44288..8c4a1586f7 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -280,7 +280,7 @@ def calculate_anomaly(clim_list, ts_list): return clim_list_final, anom_list_final -def regression(tas, cube_data, ocean_frac, land_frac, area="global"): +def regression(tas, cube_data, area, ocean_frac=None, land_frac=None): """Calculate coeffs of regression between global surf temp and variable. Parameters @@ -313,7 +313,8 @@ def regression(tas, cube_data, ocean_frac, land_frac, area="global"): tas_data = sf.area_avg_landsea( tas, ocean_frac, land_frac, land=True, return_cube=False ) - else: + + if area == 'global': # calculate global average warming tas_data = sf.area_avg(tas, return_cube=False) @@ -354,7 +355,53 @@ def regression_units(tas, cube): return units -def calculate_regressions(anom_list, ocean_frac, land_frac, area, yrs=85): +def create_cube(tas_cube, ssp_cube, array, month_number, units=None): + """Create a new cube from existing metadata, and new aray data. + + Parameters + ---------- + tas_cube: cube + near-surface air temperature + ssp_cube: cube + cube of a given variable + array: array + output array from regression + month_number: int + month related to the regression array + units: str + units related to the regression variable + + Returns + ------- + cube: cube + cube filled with regression array and metadata + + """ + # assigning dim_coords + coord1 = tas_cube.coord(contains_dimension=1) + coord2 = tas_cube.coord(contains_dimension=2) + dim_coords_and_dims = [(coord1, 0), (coord2, 1)] + + # assigning aux_coord + coord_month = iris.coords.AuxCoord(month_number, var_name="imogen_drive") + aux_coords_and_dims = [(coord_month, ())] + + cube = rename_regression_variables(ssp_cube) + + # creating cube + cube = iris.cube.Cube( + array, + units=units, + dim_coords_and_dims=dim_coords_and_dims, + aux_coords_and_dims=aux_coords_and_dims, + var_name=cube.var_name, + standard_name=cube.standard_name, + ) + + return cube + + +def calculate_regressions(anom_list, area, ocean_frac=None, land_frac=None, yrs=86): """Facilitate the calculation of regression coeffs (climate patterns). Also creates of a new cube of patterns per variable. @@ -399,50 +446,32 @@ def calculate_regressions(anom_list, ocean_frac, land_frac, area, yrs=85): month_cube_ssp = cube_ssp.extract(month_constraint) month_tas = tas.extract(month_constraint) - regr_array, score_array = regression( - month_tas, - month_cube_ssp.data, - ocean_frac, - land_frac, - area=area - ) - - # re-creating cube + if area == 'land': + regr_array, score_array = regression( + month_tas, + month_cube_ssp.data, + area=area, + ocean_frac=ocean_frac, + land_frac=land_frac, + ) + + if area == 'global': + regr_array, score_array = regression( + month_tas, + month_cube_ssp.data, + area=area, + ) + if cube.var_name in ("swdown_anom", "lwdown_anom"): units = "W m-2 K-1" else: units = regression_units(tas, cube_ssp) - - # assigning dim_coords - coord1 = tas.coord(contains_dimension=1) - coord2 = tas.coord(contains_dimension=2) - dim_coords_and_dims = [(coord1, 0), (coord2, 1)] - - # assigning aux_coord - coord_month = iris.coords.AuxCoord(i, var_name="imogen_drive") - aux_coords_and_dims = [(coord_month, ())] - - cube = rename_regression_variables(cube) - + # creating cube of regression values - regr_cube = iris.cube.Cube( - regr_array, - units=units, - dim_coords_and_dims=dim_coords_and_dims, - aux_coords_and_dims=aux_coords_and_dims, - var_name=cube.var_name, - standard_name=cube.standard_name, - ) + regr_cube = create_cube(tas, cube_ssp, regr_array, i, units=units) # calculating cube of r2 scores - score_cube = iris.cube.Cube( - score_array, - units="R2", - dim_coords_and_dims=dim_coords_and_dims, - aux_coords_and_dims=aux_coords_and_dims, - var_name=cube.var_name, - standard_name=cube.standard_name, - ) + score_cube = create_cube(tas, cube_ssp, score_array, i, units="R2") month_list.append(regr_cube) score_month_list.append(score_cube) @@ -520,31 +549,18 @@ def cube_saver(list_of_cubelists, work_path, name_list, mode): def save_outputs( - clim_list_final, - anom_list_final, - regressions, - scores, - imogen_mode, - r2_scores, - plot_path, - work_path, + cfg, + list_of_cubelists, + cube_initial ): """Save data and plots to relevant directories. Parameters ---------- - clim_list_final : cubelist - cube list of all variable climatologies - anom_list_final : cubelist - cube list of all variable anomalies - regressions : cubelist - cube list of all variable regression slopes - scores : cubelist - cube list of all variable regression scores - imogen_mode : bool - imogen_mode on or off - r2_scores : bool - determinant output on or off + cfg: dict + Dictionary passed in by ESMValTool preprocessors + list_of_cubelists: list + List of cubelists to save plot_path : str path to plot_dir work_path : str @@ -554,7 +570,10 @@ def save_outputs( ------- None """ - list_of_cubelists = [clim_list_final, anom_list_final, regressions, scores] + work_path, plot_path = sf.make_model_dirs( + cube_initial, cfg + ) + name_list = [ "climatology_variables.nc", "anomaly_variables.nc", @@ -563,10 +582,10 @@ def save_outputs( ] # saving data + plotting - if imogen_mode is True: - if r2_scores is True: + if cfg["imogen_mode"] is True: + if cfg["output_r2_scores"] is True: plot_scores(list_of_cubelists[3], plot_path) - write_scores(scores, work_path) + write_scores(list_of_cubelists[3], work_path) plot_cp_timeseries(list_of_cubelists, plot_path) cube_saver( list_of_cubelists, @@ -580,9 +599,9 @@ def save_outputs( cube_saver(list_of_cubelists, work_path, name_list, mode="imogen") else: - if r2_scores is True: + if cfg["output_r2_scores"] is True: plot_scores(list_of_cubelists[3], plot_path) - write_scores(scores, work_path) + write_scores(list_of_cubelists[3], work_path) plot_patterns(list_of_cubelists[2], plot_path) cube_saver(list_of_cubelists, work_path, name_list, mode="scores") @@ -615,39 +634,36 @@ def get_provenance_record(): return record -def patterns(model, cfg): - """Driving function for script, taking in model data and saving parameters. - +def extract_data_from_cfg(cfg, model): + """Extract model data from the cfg. + Parameters ---------- - model : str - model name cfg: dict Dictionary passed in by ESMValTool preprocessors - + model : str + model name + Returns ------- - None + clim_list: cubelist + cubelist of climatologies + ts_list: cubelist + cubelist of spatial timeseries + sftlf: cube + land fraction cube """ - input_data = cfg["input_data"].values() - grid_spec = cfg["grid"] - imogen_mode = cfg["imogen_mode"] - r2_scores = cfg["output_r2_scores"] - work_path = cfg["work_dir"] + "/" - plot_path = cfg["plot_dir"] + "/" - area = cfg["area"] - clim_list = iris.cube.CubeList([]) ts_list = iris.cube.CubeList([]) - for dataset in input_data: + for dataset in cfg["input_data"].values(): if dataset["dataset"] == model: input_file = dataset["filename"] # preparing single cube cube_initial = sf.load_cube(input_file) - if grid_spec == "constrained": + if cfg["grid"] == "constrained": cube = constrain_latitude(cube_initial) else: cube = cube_initial @@ -667,9 +683,32 @@ def patterns(model, cfg): # making climatology clim_cube = climatology(cube) clim_list.append(clim_cube) + + if cfg["area"] == 'land': + return clim_list, ts_list, sftlf + else: + return clim_list, ts_list, None + - # calculate land/ocean_fracs - ocean_frac, land_frac = sf.ocean_fraction_calc(sftlf) +def patterns(model, cfg): + """Driving function for script, taking in model data and saving parameters. + + Parameters + ---------- + model : str + model name + cfg: dict + Dictionary passed in by ESMValTool preprocessors + + Returns + ------- + None + """ + clim_list, ts_list, sftlf = extract_data_from_cfg(cfg, model) + + if cfg["area"] == 'land': + # calculate land/ocean_fracs + ocean_frac, land_frac = sf.ocean_fraction_calc(sftlf) # calculate anomaly over historical + ssp timeseries clim_list_final, anom_list_final = calculate_anomaly(clim_list, ts_list) @@ -678,23 +717,22 @@ def patterns(model, cfg): rename_clim_variables(cube) rename_anom_variables(anom_list_final[i]) - regressions, scores = calculate_regressions( - anom_list_final.copy(), ocean_frac, land_frac, area - ) - - model_work_dir, model_plot_dir = sf.make_model_dirs( - cube_initial, work_path, plot_path - ) + if cfg["area"] == 'land': + regressions, scores = calculate_regressions( + anom_list_final.copy(), cfg["area"], ocean_frac=ocean_frac, land_frac=land_frac + ) + if cfg["area"] == 'global': + regressions, scores = calculate_regressions( + anom_list_final.copy(), cfg["area"] + ) + + list_of_cubelists = [clim_list_final, anom_list_final, regressions, scores] - save_outputs( - clim_list_final, - anom_list_final, - regressions, - scores, - imogen_mode, - r2_scores, - model_plot_dir, - model_work_dir, + save_outputs(cfg, list_of_cubelists, model) + + model_work_dir, _ = sf.make_model_dirs( + cfg, + model ) provenance_record = get_provenance_record() diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index af2ce1bf18..1205f46a82 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -187,18 +187,16 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): return cube2.data -def make_model_dirs(cube_initial, work_path, plot_path): +def make_model_dirs(cfg, model): """Create directories for each input model for saving. Parameters ---------- cube_initial : cube initial input cube used to retrieve model name - work_path : path - path to work_dir - plot_path : path - path to plot_dir - + cfg: dict + Dictionary passed in by ESMValTool preprocessors + Returns ------- model_work_dir : path @@ -206,8 +204,10 @@ def make_model_dirs(cube_initial, work_path, plot_path): model_plot_dir : path path to specific plot directory in plot_dir """ - w_path = os.path.join(work_path, cube_initial.attributes["source_id"]) - p_path = os.path.join(plot_path, cube_initial.attributes["source_id"]) + work_path = cfg["work_dir"] + "/" + plot_path = cfg["plot_dir"] + "/" + w_path = os.path.join(work_path, model) + p_path = os.path.join(plot_path, model) os.mkdir(w_path) os.mkdir(p_path) @@ -238,11 +238,11 @@ def parallelise(function, processes=None): processes = 1 def easy_parallise(func, sequence, cfg): - pool = mp.Pool(processes=processes) - config_wrapper = partial(func, cfg=cfg) - result = pool.map_async(config_wrapper, sequence).get() - pool.close() - pool.join() - return result + with mp.Pool(processes=processes) as p: + config_wrapper = partial(func, cfg=cfg) + result = p.map_async(config_wrapper, sequence).get() + p.close() + p.join() + return result return partial(easy_parallise, function) diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index 29057d0f3b..0a102a3685 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -38,65 +38,61 @@ monthly_global_settings_day: &monthly_global_settings_day preprocessor: global_mean_monthly -CMIP6_landfrac: &cmip6_landfrac - - {dataset: ACCESS-CM2, exp: piControl, ensemble: r1i1p1f1, grid: gn, institute: CSIRO-ARCCSS} - - {dataset: ACCESS-ESM1-5, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: AWI-CM-1-1-MR, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: BCC-CSM2-MR, exp: hist-resIPO,ensemble: r1i1p1f1, grid: gn} - - {dataset: CanESM5, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: CanESM5-CanOE, exp: piControl, ensemble: r1i1p2f1, grid: gn} - - {dataset: CanESM5-1, exp: piControl, ensemble: r1i1p1f1, grid: gn} - # - {dataset: CAS-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use - - {dataset: CESM2, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} - - {dataset: CESM2-WACCM, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} - - {dataset: CMCC-ESM2, exp: piControl, ensemble: r1i1p1f1, grid: gn} - # - {dataset: CMCC-CM2-SR5, exp: piControl, ensemble: r1i1p1f1, grid: gn} # No tasmin/tasmax - - {dataset: CNRM-CM6-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} - - {dataset: CNRM-CM6-1-HR, exp: piControl, ensemble: r1i1p1f2, grid: gr} - - {dataset: CNRM-ESM2-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} - # - {dataset: E3SM-1-0, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Tasmax == tasmin - - {dataset: EC-Earth3, exp: piControl, ensemble: r1i1p1f1, grid: gr} - # - {dataset: EC-Earth3-CC, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use - - {dataset: EC-Earth3-Veg, exp: piControl, ensemble: r1i1p1f1, grid: gr} - # - {dataset: FGOALS-f3-L, exp: historical, ensemble: r1i1p1f1, grid: gr} # No tasmin/tasmax - - {dataset: FGOALS-g3, exp: piControl, ensemble: r1i1p1f1, grid: gn} - # - {dataset: FIO-ESM-2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use - - {dataset: GFDL-CM4, exp: piControl, ensemble: r1i1p1f1, grid: gr1} - - {dataset: GFDL-ESM4, exp: ssp370, ensemble: r1i1p1f1, grid: gr1} - - {dataset: GISS-E2-1-H, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: GISS-E2-1-G, exp: piControl, ensemble: r1i1p5f1, grid: gn} - - {dataset: GISS-E2-2-G, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: HadGEM3-GC31-LL, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: HadGEM3-GC31-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: INM-CM4-8, exp: piControl, ensemble: r1i1p1f1, grid: gr1} - - {dataset: INM-CM5-0, exp: abrupt-4xCO2, ensemble: r1i1p1f1, grid: gr1} - - {dataset: IPSL-CM6A-LR, exp: piControl, ensemble: r1i1p1f1, grid: gr} - # - {dataset: KACE-1-0-G, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use, weird tasmin/tasmax - # - {dataset: KIOST-ESM, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use - - {dataset: MIROC6, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: MIROC-ES2L, exp: piControl, ensemble: r1i1p1f2, grid: gn} - - {dataset: MIROC-ES2H, exp: piControl, ensemble: r1i1p4f2, grid: gn} - - {dataset: MPI-ESM1-2-HR, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: MPI-ESM1-2-LR, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: MRI-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: NorESM2-LM, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: NorESM2-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: TaiESM1, exp: piControl, ensemble: r1i1p1f1, grid: gn} - - {dataset: UKESM1-0-LL, exp: piControl, ensemble: r1i1p1f2, grid: gn} - -CMIP6_no_hist_tasmax: &cmip6_no_hist_tasmax - - {dataset: CESM2, exp: ssp585, ensemble: r4i1p1f1, grid: gn, start_year: 2015, end_year: 2100} - - {dataset: CESM2-WACCM, exp: ssp585, ensemble: r4i1p1f1, grid: gn, start_year: 2015, end_year: 2100} +# CMIP6_landfrac: &cmip6_landfrac +# - {dataset: ACCESS-CM2, exp: piControl, ensemble: r1i1p1f1, grid: gn, institute: CSIRO-ARCCSS} +# - {dataset: ACCESS-ESM1-5, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: AWI-CM-1-1-MR, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: BCC-CSM2-MR, exp: hist-resIPO,ensemble: r1i1p1f1, grid: gn} +# - {dataset: CanESM5, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: CanESM5-CanOE, exp: piControl, ensemble: r1i1p2f1, grid: gn} +# - {dataset: CanESM5-1, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# # - {dataset: CAS-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use +# - {dataset: CESM2, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} +# - {dataset: CESM2-WACCM, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} +# - {dataset: CMCC-ESM2, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# # - {dataset: CMCC-CM2-SR5, exp: piControl, ensemble: r1i1p1f1, grid: gn} # No tasmin/tasmax +# - {dataset: CNRM-CM6-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} +# - {dataset: CNRM-CM6-1-HR, exp: piControl, ensemble: r1i1p1f2, grid: gr} +# - {dataset: CNRM-ESM2-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} +# # - {dataset: E3SM-1-0, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Tasmax == tasmin +# - {dataset: EC-Earth3, exp: piControl, ensemble: r1i1p1f1, grid: gr} +# # - {dataset: EC-Earth3-CC, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use +# - {dataset: EC-Earth3-Veg, exp: piControl, ensemble: r1i1p1f1, grid: gr} +# # - {dataset: FGOALS-f3-L, exp: historical, ensemble: r1i1p1f1, grid: gr} # No tasmin/tasmax +# - {dataset: FGOALS-g3, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# # - {dataset: FIO-ESM-2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use +# - {dataset: GFDL-CM4, exp: piControl, ensemble: r1i1p1f1, grid: gr1} +# - {dataset: GFDL-ESM4, exp: ssp370, ensemble: r1i1p1f1, grid: gr1} +# - {dataset: GISS-E2-1-H, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: GISS-E2-1-G, exp: piControl, ensemble: r1i1p5f1, grid: gn} +# - {dataset: GISS-E2-2-G, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: HadGEM3-GC31-LL, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: HadGEM3-GC31-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: INM-CM4-8, exp: piControl, ensemble: r1i1p1f1, grid: gr1} +# - {dataset: INM-CM5-0, exp: abrupt-4xCO2, ensemble: r1i1p1f1, grid: gr1} +# - {dataset: IPSL-CM6A-LR, exp: piControl, ensemble: r1i1p1f1, grid: gr} +# # - {dataset: KACE-1-0-G, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use, weird tasmin/tasmax +# # - {dataset: KIOST-ESM, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use +# - {dataset: MIROC6, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: MIROC-ES2L, exp: piControl, ensemble: r1i1p1f2, grid: gn} +# - {dataset: MIROC-ES2H, exp: piControl, ensemble: r1i1p4f2, grid: gn} +# - {dataset: MPI-ESM1-2-HR, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: MPI-ESM1-2-LR, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: MRI-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: NorESM2-LM, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: NorESM2-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: TaiESM1, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# - {dataset: UKESM1-0-LL, exp: piControl, ensemble: r1i1p1f2, grid: gn} CMIP6_no_tasmax: &cmip6_no_tasmax - # - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2099} - - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2099} # bad tasmin/tasmax + # - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # bad tasmin/tasmax - {dataset: NorESM2-MM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: TaiESM1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} CMIP6_DAY: &cmip6_day - # - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2099} - - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2099} # bad tasmin/tasmax + # - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # bad tasmin/tasmax - {dataset: NorESM2-MM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: TaiESM1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} @@ -106,20 +102,20 @@ CMIP6_FULL: &cmip6_full - {dataset: AWI-CM-1-1-MR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: BCC-CSM2-MR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: CanESM5, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - - {dataset: CanESM5-1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: CanESM5-1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # optional extra - {dataset: CanESM5-CanOE, exp: [historical, ssp585], ensemble: r1i1p2f1, grid: gn, start_year: 1850, end_year: 2100} - # - {dataset: CAS-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: CAS-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: CMCC-ESM2, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - # - {dataset: CMCC-CM2-SR5, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: CMCC-CM2-SR5, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # No tasmin/tasmax - {dataset: CNRM-CM6-1, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} - {dataset: CNRM-CM6-1-HR, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} - {dataset: CNRM-ESM2-1, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} - {dataset: EC-Earth3, exp: [historical, ssp585], ensemble: r11i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - # - {dataset: EC-Earth3-CC, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + - {dataset: EC-Earth3-CC, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - {dataset: EC-Earth3-Veg, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - # - {dataset: FGOALS-f3-L, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + # - {dataset: FGOALS-f3-L, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} # No tasmin/tasmax - {dataset: FGOALS-g3, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - # - {dataset: FIO-ESM-2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: FIO-ESM-2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: GFDL-CM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: GFDL-ESM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: GISS-E2-1-H, exp: [historical, ssp585], ensemble: r3i1p1f2, grid: gn, start_year: 1850, end_year: 2100} @@ -130,11 +126,11 @@ CMIP6_FULL: &cmip6_full - {dataset: INM-CM4-8, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: INM-CM5-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: IPSL-CM6A-LR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - # - {dataset: KACE-1-0-G, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - # - {dataset: KIOST-ESM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + # - {dataset: KACE-1-0-G, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} # bad tasmin/tasmax + # - {dataset: KIOST-ESM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} # optional extra - {dataset: MIROC6, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MIROC-ES2L, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gn, start_year: 1850, end_year: 2100} - - {dataset: MIROC-ES2H, exp: [historical, ssp585], ensemble: r1i1p4f2, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: MIROC-ES2H, exp: [historical, ssp585], ensemble: r1i1p4f2, grid: gn, start_year: 1850, end_year: 2100} # optional extra - {dataset: MPI-ESM1-2-HR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MPI-ESM1-2-LR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MRI-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} @@ -146,12 +142,12 @@ diagnostics: variables: - sftlf: - short_name: sftlf - mip: fx - project: CMIP6 - preprocessor: downscale_sftlf - additional_datasets: *cmip6_landfrac + # sftlf: + # short_name: sftlf + # mip: fx + # project: CMIP6 + # preprocessor: downscale_sftlf + # additional_datasets: *cmip6_landfrac tasmax_585: short_name: tasmax @@ -243,51 +239,6 @@ diagnostics: <<: *monthly_global_settings additional_datasets: *cmip6_no_tasmax - tasmax_no_hist_tasmax: - short_name: tasmax - <<: *monthly_global_settings - additional_datasets: *cmip6_no_hist_tasmax - - tasmin_no_hist_tasmax: - short_name: tasmin - <<: *monthly_global_settings - additional_datasets: *cmip6_no_hist_tasmax - - tas_no_hist_tasmax: - short_name: tas - <<: *monthly_global_settings - additional_datasets: *cmip6_no_hist_tasmax - - huss_no_hist_tasmax: - short_name: huss - <<: *monthly_global_settings - additional_datasets: *cmip6_no_hist_tasmax - - pr_no_hist_tasmax: - short_name: pr - <<: *monthly_global_settings - additional_datasets: *cmip6_no_hist_tasmax - - sfcWind_no_hist_tasmax: - short_name: sfcWind - <<: *monthly_global_settings - additional_datasets: *cmip6_no_hist_tasmax - - ps_no_hist_tasmax: - short_name: ps - <<: *monthly_global_settings - additional_datasets: *cmip6_no_hist_tasmax - - rsds_no_hist_tasmax: - short_name: rsds - <<: *monthly_global_settings - additional_datasets: *cmip6_no_hist_tasmax - - rlds_no_hist_tasmax: - short_name: rlds - <<: *monthly_global_settings - additional_datasets: *cmip6_no_hist_tasmax - scripts: climate_patterns_script: script: climate_patterns/climate_patterns.py @@ -295,5 +246,5 @@ diagnostics: imogen_mode: on # options: on, off output_r2_scores: on # options: on, off parallelise: on # options: on, off - parallel_threads: 40 # int, optional - area: land # options global, land + parallel_threads: 38 # int, optional + area: global # options global, land. If land, uncomment landfrac recipe settings From b599bdccd2d708504fc769a371e6d36fd74686eb Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 8 Feb 2024 15:26:28 +0000 Subject: [PATCH 27/70] flake8 fixes --- .../climate_patterns/climate_patterns.py | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 8c4a1586f7..8fb547757f 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -313,7 +313,7 @@ def regression(tas, cube_data, area, ocean_frac=None, land_frac=None): tas_data = sf.area_avg_landsea( tas, ocean_frac, land_frac, land=True, return_cube=False ) - + if area == 'global': # calculate global average warming tas_data = sf.area_avg(tas, return_cube=False) @@ -357,7 +357,7 @@ def regression_units(tas, cube): def create_cube(tas_cube, ssp_cube, array, month_number, units=None): """Create a new cube from existing metadata, and new aray data. - + Parameters ---------- tas_cube: cube @@ -370,12 +370,12 @@ def create_cube(tas_cube, ssp_cube, array, month_number, units=None): month related to the regression array units: str units related to the regression variable - + Returns ------- cube: cube cube filled with regression array and metadata - + """ # assigning dim_coords coord1 = tas_cube.coord(contains_dimension=1) @@ -388,7 +388,7 @@ def create_cube(tas_cube, ssp_cube, array, month_number, units=None): cube = rename_regression_variables(ssp_cube) - # creating cube + # creating cube cube = iris.cube.Cube( array, units=units, @@ -397,11 +397,17 @@ def create_cube(tas_cube, ssp_cube, array, month_number, units=None): var_name=cube.var_name, standard_name=cube.standard_name, ) - + return cube -def calculate_regressions(anom_list, area, ocean_frac=None, land_frac=None, yrs=86): +def calculate_regressions( + anom_list, + area, + ocean_frac=None, + land_frac=None, + yrs=86 +): """Facilitate the calculation of regression coeffs (climate patterns). Also creates of a new cube of patterns per variable. @@ -454,19 +460,19 @@ def calculate_regressions(anom_list, area, ocean_frac=None, land_frac=None, yrs= ocean_frac=ocean_frac, land_frac=land_frac, ) - + if area == 'global': regr_array, score_array = regression( month_tas, month_cube_ssp.data, area=area, ) - + if cube.var_name in ("swdown_anom", "lwdown_anom"): units = "W m-2 K-1" else: units = regression_units(tas, cube_ssp) - + # creating cube of regression values regr_cube = create_cube(tas, cube_ssp, regr_array, i, units=units) @@ -573,7 +579,7 @@ def save_outputs( work_path, plot_path = sf.make_model_dirs( cube_initial, cfg ) - + name_list = [ "climatology_variables.nc", "anomaly_variables.nc", @@ -636,14 +642,14 @@ def get_provenance_record(): def extract_data_from_cfg(cfg, model): """Extract model data from the cfg. - + Parameters ---------- cfg: dict Dictionary passed in by ESMValTool preprocessors model : str model name - + Returns ------- clim_list: cubelist @@ -683,8 +689,8 @@ def extract_data_from_cfg(cfg, model): # making climatology clim_cube = climatology(cube) clim_list.append(clim_cube) - - if cfg["area"] == 'land': + + if cfg["area"] == 'land': return clim_list, ts_list, sftlf else: return clim_list, ts_list, None @@ -719,19 +725,22 @@ def patterns(model, cfg): if cfg["area"] == 'land': regressions, scores = calculate_regressions( - anom_list_final.copy(), cfg["area"], ocean_frac=ocean_frac, land_frac=land_frac + anom_list_final.copy(), + cfg["area"], + ocean_frac=ocean_frac, + land_frac=land_frac ) if cfg["area"] == 'global': regressions, scores = calculate_regressions( anom_list_final.copy(), cfg["area"] ) - + list_of_cubelists = [clim_list_final, anom_list_final, regressions, scores] save_outputs(cfg, list_of_cubelists, model) - + model_work_dir, _ = sf.make_model_dirs( - cfg, + cfg, model ) From 48c04e3ee3c0de039cb03cbec31365f53992e7c4 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 8 Feb 2024 15:27:46 +0000 Subject: [PATCH 28/70] more fixes --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 1205f46a82..22920dfa50 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -196,7 +196,7 @@ def make_model_dirs(cfg, model): initial input cube used to retrieve model name cfg: dict Dictionary passed in by ESMValTool preprocessors - + Returns ------- model_work_dir : path From a06502e4bfe9ff635dca99d856a60b39d59fa5ee Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 8 Feb 2024 15:41:10 +0000 Subject: [PATCH 29/70] Multiprocessing change --- esmvaltool/diag_scripts/climate_patterns/climate_patterns.py | 3 +-- esmvaltool/recipes/recipe_climate_patterns.yml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 8fb547757f..66a69411cb 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -764,7 +764,6 @@ def main(cfg): """ input_data = cfg["input_data"].values() parallelise = cfg["parallelise"] - threads = cfg["parallel_threads"] models = [] for mod in input_data: @@ -773,7 +772,7 @@ def main(cfg): models.append(model) if parallelise is True: - sf.parallelise(patterns, threads)(models, cfg) + sf.parallelise(patterns)(models, cfg) else: for model in models: patterns(model, cfg) diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index 0a102a3685..3ce9ce120f 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -246,5 +246,4 @@ diagnostics: imogen_mode: on # options: on, off output_r2_scores: on # options: on, off parallelise: on # options: on, off - parallel_threads: 38 # int, optional area: global # options global, land. If land, uncomment landfrac recipe settings From c5279892eb2e29e3b94ccf2c3704581c8e4458d3 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 8 Feb 2024 15:52:18 +0000 Subject: [PATCH 30/70] Codacy fixes --- .../climate_patterns/climate_patterns.py | 14 ++++++-------- .../diag_scripts/climate_patterns/sub_functions.py | 12 ++++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 66a69411cb..d5ba409cfd 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -557,7 +557,7 @@ def cube_saver(list_of_cubelists, work_path, name_list, mode): def save_outputs( cfg, list_of_cubelists, - cube_initial + model ): """Save data and plots to relevant directories. @@ -567,17 +567,15 @@ def save_outputs( Dictionary passed in by ESMValTool preprocessors list_of_cubelists: list List of cubelists to save - plot_path : str - path to plot_dir - work_path : str - path to work_dir + model : str + model name Returns ------- None """ work_path, plot_path = sf.make_model_dirs( - cube_initial, cfg + cfg, model ) name_list = [ @@ -692,8 +690,8 @@ def extract_data_from_cfg(cfg, model): if cfg["area"] == 'land': return clim_list, ts_list, sftlf - else: - return clim_list, ts_list, None + + return clim_list, ts_list, None def patterns(model, cfg): diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 22920dfa50..169d3c8fe2 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -192,10 +192,10 @@ def make_model_dirs(cfg, model): Parameters ---------- - cube_initial : cube - initial input cube used to retrieve model name cfg: dict Dictionary passed in by ESMValTool preprocessors + model : str + model name Returns ------- @@ -238,11 +238,11 @@ def parallelise(function, processes=None): processes = 1 def easy_parallise(func, sequence, cfg): - with mp.Pool(processes=processes) as p: + with mp.Pool(processes=processes) as pool: config_wrapper = partial(func, cfg=cfg) - result = p.map_async(config_wrapper, sequence).get() - p.close() - p.join() + result = pool.map_async(config_wrapper, sequence).get() + pool.close() + pool.join() return result return partial(easy_parallise, function) From e8f5ee5298642dbca4615e68f42fe7068cbf65fc Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 8 Feb 2024 16:24:45 +0000 Subject: [PATCH 31/70] Broke up plotting function --- .../climate_patterns/climate_patterns.py | 16 ++- .../diag_scripts/climate_patterns/plotting.py | 135 +++++++++++------- 2 files changed, 99 insertions(+), 52 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index d5ba409cfd..51be443fa3 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -43,7 +43,13 @@ import numpy as np import sklearn.linear_model import sub_functions as sf -from plotting import plot_cp_timeseries, plot_patterns, plot_scores +from plotting import ( + plot_patterns_timeseries_and_maps, + plot_anomalies_timeseries, + plot_climatologies_timeseries, + plot_patterns, + plot_scores +) from rename_variables import ( rename_anom_variables, rename_clim_variables, @@ -590,7 +596,9 @@ def save_outputs( if cfg["output_r2_scores"] is True: plot_scores(list_of_cubelists[3], plot_path) write_scores(list_of_cubelists[3], work_path) - plot_cp_timeseries(list_of_cubelists, plot_path) + plot_climatologies_timeseries(list_of_cubelists[0], plot_path) + plot_anomalies_timeseries(list_of_cubelists[1], plot_path) + plot_patterns_timeseries_and_maps(list_of_cubelists[2], plot_path) cube_saver( list_of_cubelists, work_path, @@ -599,7 +607,9 @@ def save_outputs( ) else: - plot_cp_timeseries(list_of_cubelists, plot_path) + plot_climatologies_timeseries(list_of_cubelists[0], plot_path) + plot_anomalies_timeseries(list_of_cubelists[1], plot_path) + plot_patterns_timeseries_and_maps(list_of_cubelists[2], plot_path) cube_saver(list_of_cubelists, work_path, name_list, mode="imogen") else: diff --git a/esmvaltool/diag_scripts/climate_patterns/plotting.py b/esmvaltool/diag_scripts/climate_patterns/plotting.py index 7ea93afb81..7bb7ac319a 100644 --- a/esmvaltool/diag_scripts/climate_patterns/plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/plotting.py @@ -84,12 +84,12 @@ def plot_patterns(cube_list, plot_path): fig.savefig(plot_path + "Patterns Timeseries") -def plot_cp_timeseries(list_cubelists, plot_path): +def plot_patterns_timeseries_and_maps(cubelist, plot_path): """Plot timeseries and maps of climatologies, anomalies and patterns. Parameters ---------- - list_cubelists : cubelist + cubelist: cubelist input cubelist for plotting per variable plot_path : path path to plot_dir @@ -98,65 +98,102 @@ def plot_cp_timeseries(list_cubelists, plot_path): ------- None """ - fig1, ax1 = plt.subplots(3, 3, figsize=(14, 12), sharex=True) - fig1.suptitle("40 Year Climatologies, 1850-1889", fontsize=18, y=0.98) - - fig2, ax2 = plt.subplots(3, 3, figsize=(14, 12), sharex=True) - fig2.suptitle("Anomaly Timeseries, 1850-2100", fontsize=18, y=0.98) - - fig3, ax3 = plt.subplots(3, 3, figsize=(14, 12), sharex=True) - fig3.suptitle("Patterns from a random grid-cell", fontsize=18, y=0.98) + fig, ax = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig.suptitle("Patterns from a random grid-cell", fontsize=18, y=0.98) plt.figure(figsize=(14, 12)) plt.subplots_adjust(hspace=0.5) plt.suptitle("Global Patterns, January", fontsize=18, y=0.95) - for i, cube_list in enumerate(list_cubelists): - for j, cube in enumerate(cube_list): - # determining plot positions - x_pos, y_pos = subplot_positions(j) - yrs = (1850 + np.arange(cube.shape[0])).astype("float") - if i == 0: - # climatology - avg_cube = sf.area_avg(cube, return_cube=False) - ax1[x_pos, y_pos].plot(yrs, avg_cube) - ax1[x_pos, - y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) - if j > 5: - ax1[x_pos, y_pos].set_xlabel("Time") - if i == 1: - # anomaly timeseries - avg_cube = sf.area_avg(cube, return_cube=False) - ax2[x_pos, y_pos].plot(yrs, avg_cube) - ax2[x_pos, - y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) - if j > 5: - ax2[x_pos, y_pos].set_xlabel("Time") - if i == 2: - months = np.arange(1, 13) - # avg_cube = sf.area_avg(cube, return_cube=False) - ax3[x_pos, y_pos].plot(months, cube[:, 50, 50].data) - ax3[x_pos, y_pos].set_ylabel( - str(cube.var_name) + " / " + str(cube.units)) - if j > 5: - ax3[x_pos, y_pos].set_xlabel("Time") - if i == 2: - # January patterns - plt.subplot(3, 3, j + 1) - qplt.pcolormesh(cube[0]) + for j, cube in enumerate(cubelist): + # determining plot positions + x_pos, y_pos = subplot_positions(j) + + months = np.arange(1, 13) + # avg_cube = sf.area_avg(cube, return_cube=False) + ax[x_pos, y_pos].plot(months, cube[:, 50, 50].data) + ax[x_pos, y_pos].set_ylabel( + str(cube.var_name) + " / " + str(cube.units)) + if j > 5: + ax[x_pos, y_pos].set_xlabel("Time") + + # January patterns + plt.subplot(3, 3, j + 1) + qplt.pcolormesh(cube[0]) + + fig.tight_layout() + fig.savefig(plot_path + "Patterns Timeseries") plt.tight_layout() plt.savefig(plot_path + "Patterns") plt.close() - fig1.tight_layout() - fig1.savefig(plot_path + "Climatologies") - fig2.tight_layout() - fig2.savefig(plot_path + "Anomalies") +def plot_anomalies_timeseries(cubelist, plot_path): + """Plot timeseries and maps of climatologies, anomalies and patterns. + + Parameters + ---------- + cubelist : cubelist + input cubelist for plotting per variable + plot_path : path + path to plot_dir + + Returns + ------- + None + """ + fig, ax = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig.suptitle("Anomaly Timeseries, 1850-2100", fontsize=18, y=0.98) + + for j, cube in enumerate(cubelist): + # determining plot positions + x_pos, y_pos = subplot_positions(j) + yrs = (1850 + np.arange(cube.shape[0])).astype("float") + + # anomaly timeseries + avg_cube = sf.area_avg(cube, return_cube=False) + ax[x_pos, y_pos].plot(yrs, avg_cube) + ax[x_pos, + y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) + if j > 5: + ax[x_pos, y_pos].set_xlabel("Time") + + fig.tight_layout() + fig.savefig(plot_path + "Anomalies") + + +def plot_climatologies_timeseries(cubelist, plot_path): + """Plot timeseries and maps of climatologies, anomalies and patterns. - fig3.tight_layout() - fig3.savefig(plot_path + "Patterns Timeseries") + Parameters + ---------- + cubelist : cubelist + input cubelist for plotting per variable + plot_path : path + path to plot_dir + + Returns + ------- + None + """ + fig, ax = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig.suptitle("40 Year Climatologies, 1850-1889", fontsize=18, y=0.98) + + for j, cube in enumerate(cubelist): + # determining plot positions + x_pos, y_pos = subplot_positions(j) + yrs = (1850 + np.arange(cube.shape[0])).astype("float") + + avg_cube = sf.area_avg(cube, return_cube=False) + ax[x_pos, y_pos].plot(yrs, avg_cube) + ax[x_pos, + y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) + if j > 5: + ax[x_pos, y_pos].set_xlabel("Time") + + fig.tight_layout() + fig.savefig(plot_path + "Climatologies") def plot_scores(cube_list, plot_path): From 271dad778e60154dd8ba081a4d7b454dec20807c Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 8 Feb 2024 16:34:42 +0000 Subject: [PATCH 32/70] Codacy fixes --- .../diag_scripts/climate_patterns/plotting.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/plotting.py b/esmvaltool/diag_scripts/climate_patterns/plotting.py index 7bb7ac319a..0b8c3baedf 100644 --- a/esmvaltool/diag_scripts/climate_patterns/plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/plotting.py @@ -84,7 +84,7 @@ def plot_patterns(cube_list, plot_path): fig.savefig(plot_path + "Patterns Timeseries") -def plot_patterns_timeseries_and_maps(cubelist, plot_path): +def plot_patterns_timeseries(cubelist, plot_path): """Plot timeseries and maps of climatologies, anomalies and patterns. Parameters @@ -98,7 +98,7 @@ def plot_patterns_timeseries_and_maps(cubelist, plot_path): ------- None """ - fig, ax = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig, axs = plt.subplots(3, 3, figsize=(14, 12), sharex=True) fig.suptitle("Patterns from a random grid-cell", fontsize=18, y=0.98) plt.figure(figsize=(14, 12)) @@ -111,11 +111,11 @@ def plot_patterns_timeseries_and_maps(cubelist, plot_path): months = np.arange(1, 13) # avg_cube = sf.area_avg(cube, return_cube=False) - ax[x_pos, y_pos].plot(months, cube[:, 50, 50].data) - ax[x_pos, y_pos].set_ylabel( + axs[x_pos, y_pos].plot(months, cube[:, 50, 50].data) + axs[x_pos, y_pos].set_ylabel( str(cube.var_name) + " / " + str(cube.units)) if j > 5: - ax[x_pos, y_pos].set_xlabel("Time") + axs[x_pos, y_pos].set_xlabel("Time") # January patterns plt.subplot(3, 3, j + 1) @@ -143,7 +143,7 @@ def plot_anomalies_timeseries(cubelist, plot_path): ------- None """ - fig, ax = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig, axs = plt.subplots(3, 3, figsize=(14, 12), sharex=True) fig.suptitle("Anomaly Timeseries, 1850-2100", fontsize=18, y=0.98) for j, cube in enumerate(cubelist): @@ -153,11 +153,11 @@ def plot_anomalies_timeseries(cubelist, plot_path): # anomaly timeseries avg_cube = sf.area_avg(cube, return_cube=False) - ax[x_pos, y_pos].plot(yrs, avg_cube) - ax[x_pos, + axs[x_pos, y_pos].plot(yrs, avg_cube) + axs[x_pos, y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) if j > 5: - ax[x_pos, y_pos].set_xlabel("Time") + axs[x_pos, y_pos].set_xlabel("Time") fig.tight_layout() fig.savefig(plot_path + "Anomalies") @@ -177,7 +177,7 @@ def plot_climatologies_timeseries(cubelist, plot_path): ------- None """ - fig, ax = plt.subplots(3, 3, figsize=(14, 12), sharex=True) + fig, axs = plt.subplots(3, 3, figsize=(14, 12), sharex=True) fig.suptitle("40 Year Climatologies, 1850-1889", fontsize=18, y=0.98) for j, cube in enumerate(cubelist): @@ -186,11 +186,11 @@ def plot_climatologies_timeseries(cubelist, plot_path): yrs = (1850 + np.arange(cube.shape[0])).astype("float") avg_cube = sf.area_avg(cube, return_cube=False) - ax[x_pos, y_pos].plot(yrs, avg_cube) - ax[x_pos, + axs[x_pos, y_pos].plot(yrs, avg_cube) + axs[x_pos, y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) if j > 5: - ax[x_pos, y_pos].set_xlabel("Time") + axs[x_pos, y_pos].set_xlabel("Time") fig.tight_layout() fig.savefig(plot_path + "Climatologies") From 8a4c30a54e6a81bb63e9aabde2aab46e10b291f4 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 9 Feb 2024 10:26:32 +0000 Subject: [PATCH 33/70] Couple of fixes --- .../diag_scripts/climate_patterns/climate_patterns.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 51be443fa3..1e296579b7 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -44,7 +44,7 @@ import sklearn.linear_model import sub_functions as sf from plotting import ( - plot_patterns_timeseries_and_maps, + plot_patterns_timeseries, plot_anomalies_timeseries, plot_climatologies_timeseries, plot_patterns, @@ -598,7 +598,7 @@ def save_outputs( write_scores(list_of_cubelists[3], work_path) plot_climatologies_timeseries(list_of_cubelists[0], plot_path) plot_anomalies_timeseries(list_of_cubelists[1], plot_path) - plot_patterns_timeseries_and_maps(list_of_cubelists[2], plot_path) + plot_patterns_timeseries(list_of_cubelists[2], plot_path) cube_saver( list_of_cubelists, work_path, @@ -609,7 +609,7 @@ def save_outputs( else: plot_climatologies_timeseries(list_of_cubelists[0], plot_path) plot_anomalies_timeseries(list_of_cubelists[1], plot_path) - plot_patterns_timeseries_and_maps(list_of_cubelists[2], plot_path) + plot_patterns_timeseries(list_of_cubelists[2], plot_path) cube_saver(list_of_cubelists, work_path, name_list, mode="imogen") else: From 53f502c82aa7d03d1cf96e8c79481666c856e26f Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 9 Feb 2024 12:24:09 +0000 Subject: [PATCH 34/70] Fixed mkdir bug --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 169d3c8fe2..bb9d58aa44 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -208,8 +208,11 @@ def make_model_dirs(cfg, model): plot_path = cfg["plot_dir"] + "/" w_path = os.path.join(work_path, model) p_path = os.path.join(plot_path, model) - os.mkdir(w_path) - os.mkdir(p_path) + + if not os.path.exists(w_path): + os.mkdir(w_path) + if not os.path.exists(p_path): + os.mkdir(p_path) model_work_dir = w_path + "/" model_plot_dir = p_path + "/" From d9ca55efe03826a921acbbfe402f7a1bef9db2cd Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Tue, 13 Feb 2024 15:24:15 +0000 Subject: [PATCH 35/70] Removed scores, refactored --- .../climate_patterns/climate_patterns.py | 132 ++++-------------- .../diag_scripts/climate_patterns/plotting.py | 49 +------ .../climate_patterns/sub_functions.py | 36 +---- .../recipes/recipe_climate_patterns.yml | 3 +- 4 files changed, 34 insertions(+), 186 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 1e296579b7..d732ea6ade 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -21,9 +21,6 @@ options: on, off def: outputs extra data (anomaly, climatology) per variable to drive JULES-IMOGEN configuration -output_r2_scores: bool, optional (default: off) - options: on, off - def: outputs determinant values per variable to measure pattern robustness parallelise: bool, optional (default: off) options: on, off def: parallelises code to run N models at once @@ -47,8 +44,7 @@ plot_patterns_timeseries, plot_anomalies_timeseries, plot_climatologies_timeseries, - plot_patterns, - plot_scores + plot_patterns ) from rename_variables import ( rename_anom_variables, @@ -58,6 +54,7 @@ ) from esmvaltool.diag_scripts.shared import ProvenanceLogger, run_diagnostic +from esmvalcore.preprocessor import area_statistics logger = logging.getLogger(Path(__file__).stem) @@ -307,13 +304,7 @@ def regression(tas, cube_data, area, ocean_frac=None, land_frac=None): slope_array : arr array of grid cells with same shape as initial cube, containing the regression slope - score_array : arr - array of grid cells with same shape as initial cube, - containing the regression score as a measure of robustness """ - slope_array = np.full(tas.data.shape[1:], np.nan) - score_array = np.full(tas.data.shape[1:], np.nan) - if area == "land": # calculate average warming over land tas_data = sf.area_avg_landsea( @@ -322,23 +313,24 @@ def regression(tas, cube_data, area, ocean_frac=None, land_frac=None): if area == 'global': # calculate global average warming - tas_data = sf.area_avg(tas, return_cube=False) + tas_data = area_statistics(tas, 'mean').data - for i in range(tas.data.shape[1]): - for j in range(tas.data.shape[2]): - if tas.data[0, i, j] is not np.ma.masked: - model = sklearn.linear_model.LinearRegression( - fit_intercept=False, copy_X=True - ) + # Reshape cube for regression + cube_reshaped = cube_data.reshape(cube_data.shape[0], -1) + + # Perform linear regression on valid values + model = sklearn.linear_model.LinearRegression( + fit_intercept=False, copy_X=True + ) + model.fit(tas_data.reshape(-1, 1), cube_reshaped) - x_val = tas_data.reshape(-1, 1) - y_val = cube_data[:, i, j] + # Extract regression coefficients + slopes = model.coef_ - model.fit(x_val, y_val) - slope_array[i, j] = model.coef_ - score_array[i, j] = model.score(x_val, y_val) + # Reshape the regression coefficients back to the shape of the grid cells + slope_array = slopes.reshape(cube_data.shape[1:]) - return slope_array, score_array + return slope_array def regression_units(tas, cube): @@ -435,11 +427,8 @@ def calculate_regressions( ------- regr_var_list : cubelist cube list of newly created regression slope value cubes, for each var - score_list : cubelist - cube list of newly created regression score cubes, for each var """ regr_var_list = iris.cube.CubeList([]) - score_list = iris.cube.CubeList([]) months = yrs * 12 for cube in anom_list: @@ -450,7 +439,6 @@ def calculate_regressions( for cube in anom_list: cube_ssp = cube[-months:] month_list = iris.cube.CubeList([]) - score_month_list = iris.cube.CubeList([]) # extracting months, regressing, and merging for i in range(1, 13): @@ -459,7 +447,7 @@ def calculate_regressions( month_tas = tas.extract(month_constraint) if area == 'land': - regr_array, score_array = regression( + regr_array = regression( month_tas, month_cube_ssp.data, area=area, @@ -468,7 +456,7 @@ def calculate_regressions( ) if area == 'global': - regr_array, score_array = regression( + regr_array = regression( month_tas, month_cube_ssp.data, area=area, @@ -482,44 +470,12 @@ def calculate_regressions( # creating cube of regression values regr_cube = create_cube(tas, cube_ssp, regr_array, i, units=units) - # calculating cube of r2 scores - score_cube = create_cube(tas, cube_ssp, score_array, i, units="R2") - month_list.append(regr_cube) - score_month_list.append(score_cube) conc_cube = month_list.merge_cube() regr_var_list.append(conc_cube) - conc_score_cube = score_month_list.merge_cube() - score_list.append(conc_score_cube) - - return regr_var_list, score_list - - -def write_scores(scores, work_path): - """Save the global average regression scores per variable in a text file. - - Parameters - ---------- - scores : cubelist - cube list of regression score cubes, for each variable - work_path : path - path to work_dir, to save scores - - Returns - ------- - None - """ - for cube in scores: - score = sf.area_avg(cube, return_cube=False) - mean_score = np.mean(score) - data = f"{mean_score:10.3f}" - name = cube.var_name - # saving scores - with open(work_path + "scores", "a", encoding='utf-8') as file: - file.write(name + ": " + data + "\n") - file.close() + return regr_var_list def cube_saver(list_of_cubelists, work_path, name_list, mode): @@ -540,20 +496,10 @@ def cube_saver(list_of_cubelists, work_path, name_list, mode): ------- None """ - if mode == "imogen_scores": - for i in range(0, 4): - iris.save(list_of_cubelists[i], work_path + name_list[i]) - if mode == "imogen": for i in range(0, 3): iris.save(list_of_cubelists[i], work_path + name_list[i]) - if mode == "scores": - for i in range(2, 4): - for cube in list_of_cubelists[i]: - rename_variables_base(cube) - iris.save(list_of_cubelists[i], work_path + name_list[i]) - if mode == "base": for cube in list_of_cubelists[2]: rename_variables_base(cube) @@ -588,40 +534,18 @@ def save_outputs( "climatology_variables.nc", "anomaly_variables.nc", "patterns.nc", - "scores.nc", ] # saving data + plotting if cfg["imogen_mode"] is True: - if cfg["output_r2_scores"] is True: - plot_scores(list_of_cubelists[3], plot_path) - write_scores(list_of_cubelists[3], work_path) - plot_climatologies_timeseries(list_of_cubelists[0], plot_path) - plot_anomalies_timeseries(list_of_cubelists[1], plot_path) - plot_patterns_timeseries(list_of_cubelists[2], plot_path) - cube_saver( - list_of_cubelists, - work_path, - name_list, - mode="imogen_scores" - ) - - else: - plot_climatologies_timeseries(list_of_cubelists[0], plot_path) - plot_anomalies_timeseries(list_of_cubelists[1], plot_path) - plot_patterns_timeseries(list_of_cubelists[2], plot_path) - cube_saver(list_of_cubelists, work_path, name_list, mode="imogen") + plot_climatologies_timeseries(list_of_cubelists[0], plot_path) + plot_anomalies_timeseries(list_of_cubelists[1], plot_path) + plot_patterns_timeseries(list_of_cubelists[2], plot_path) + cube_saver(list_of_cubelists, work_path, name_list, mode="imogen") else: - if cfg["output_r2_scores"] is True: - plot_scores(list_of_cubelists[3], plot_path) - write_scores(list_of_cubelists[3], work_path) - plot_patterns(list_of_cubelists[2], plot_path) - cube_saver(list_of_cubelists, work_path, name_list, mode="scores") - - else: - plot_patterns(list_of_cubelists[2], plot_path) - cube_saver(list_of_cubelists, work_path, name_list, mode="base") + plot_patterns(list_of_cubelists[2], plot_path) + cube_saver(list_of_cubelists, work_path, name_list, mode="base") def get_provenance_record(): @@ -732,18 +656,18 @@ def patterns(model, cfg): rename_anom_variables(anom_list_final[i]) if cfg["area"] == 'land': - regressions, scores = calculate_regressions( + regressions = calculate_regressions( anom_list_final.copy(), cfg["area"], ocean_frac=ocean_frac, land_frac=land_frac ) if cfg["area"] == 'global': - regressions, scores = calculate_regressions( + regressions = calculate_regressions( anom_list_final.copy(), cfg["area"] ) - list_of_cubelists = [clim_list_final, anom_list_final, regressions, scores] + list_of_cubelists = [clim_list_final, anom_list_final, regressions] save_outputs(cfg, list_of_cubelists, model) diff --git a/esmvaltool/diag_scripts/climate_patterns/plotting.py b/esmvaltool/diag_scripts/climate_patterns/plotting.py index 0b8c3baedf..b4012b3740 100644 --- a/esmvaltool/diag_scripts/climate_patterns/plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/plotting.py @@ -4,12 +4,11 @@ ------ Gregory Munday (Met Office, UK) """ - -import iris.plot as iplt import iris.quickplot as qplt import matplotlib.pyplot as plt import numpy as np -import sub_functions as sf + +from esmvalcore.preprocessor import area_statistics def subplot_positions(j): @@ -110,7 +109,6 @@ def plot_patterns_timeseries(cubelist, plot_path): x_pos, y_pos = subplot_positions(j) months = np.arange(1, 13) - # avg_cube = sf.area_avg(cube, return_cube=False) axs[x_pos, y_pos].plot(months, cube[:, 50, 50].data) axs[x_pos, y_pos].set_ylabel( str(cube.var_name) + " / " + str(cube.units)) @@ -152,7 +150,7 @@ def plot_anomalies_timeseries(cubelist, plot_path): yrs = (1850 + np.arange(cube.shape[0])).astype("float") # anomaly timeseries - avg_cube = sf.area_avg(cube, return_cube=False) + avg_cube = area_statistics(cube, 'mean') axs[x_pos, y_pos].plot(yrs, avg_cube) axs[x_pos, y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) @@ -185,7 +183,7 @@ def plot_climatologies_timeseries(cubelist, plot_path): x_pos, y_pos = subplot_positions(j) yrs = (1850 + np.arange(cube.shape[0])).astype("float") - avg_cube = sf.area_avg(cube, return_cube=False) + avg_cube = area_statistics(cube, 'mean') axs[x_pos, y_pos].plot(yrs, avg_cube) axs[x_pos, y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) @@ -194,42 +192,3 @@ def plot_climatologies_timeseries(cubelist, plot_path): fig.tight_layout() fig.savefig(plot_path + "Climatologies") - - -def plot_scores(cube_list, plot_path): - """Plot color mesh of scores per variable per month. - - Parameters - ---------- - cube_list : cube - input cubelist for plotting - plot_path : path - path to plot_dir - - Returns - ------- - None - """ - for cube in cube_list: - plt.figure(figsize=(14, 12)) - plt.subplots_adjust(hspace=0.5) - plt.suptitle("Scores " + cube.var_name, fontsize=18, y=0.98) - for j in range(0, 12): - plt.subplot(4, 3, j + 1) - qplt.pcolormesh(cube[j]) - - plt.tight_layout() - plt.savefig(plot_path + "R2_Scores_" + str(cube.var_name)) - plt.close() - - # plot global scores timeseries per variable - plt.figure(figsize=(5, 8)) - for cube in cube_list: - score = sf.area_avg(cube, return_cube=True) - iplt.plot(score, label=cube.var_name) - plt.xlabel("Time") - plt.ylabel("R2 Score") - plt.legend(loc="center left") - - plt.savefig(plot_path + "score_timeseries") - plt.close() diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index bb9d58aa44..38bc148aea 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -39,40 +39,6 @@ def load_cube(filename): return cube -def area_avg(cube, return_cube=None): - """Calculate the global mean of a variable in a cube, area-weighted. - - Parameters - ---------- - cube : cube - input cube - return_cube : bool - option to return a cube or array - - Returns - ------- - cube2 : cube - cube with collapsed lat-lons, global mean over time - cube2.data : arr - array with collapsed lat-lons, global mean over time - """ - if not cube.coord("latitude").has_bounds(): - cube.coord("latitude").guess_bounds() - if not cube.coord("longitude").has_bounds(): - cube.coord("longitude").guess_bounds() - area = iris.analysis.cartography.area_weights(cube, normalize=False) - cube2 = cube.collapsed( - ["latitude", "longitude"], - iris.analysis.MEAN, - weights=area - ) - - if return_cube: - return cube2 - - return cube2.data - - def ocean_fraction_calc(sftlf): """Calculate gridded land and ocean fractions. @@ -208,7 +174,7 @@ def make_model_dirs(cfg, model): plot_path = cfg["plot_dir"] + "/" w_path = os.path.join(work_path, model) p_path = os.path.join(plot_path, model) - + if not os.path.exists(w_path): os.mkdir(w_path) if not os.path.exists(p_path): diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index 3ce9ce120f..8e67099260 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -114,7 +114,7 @@ CMIP6_FULL: &cmip6_full - {dataset: EC-Earth3-CC, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - {dataset: EC-Earth3-Veg, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} # - {dataset: FGOALS-f3-L, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} # No tasmin/tasmax - - {dataset: FGOALS-g3, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: FGOALS-g3, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: FIO-ESM-2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: GFDL-CM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: GFDL-ESM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} @@ -244,6 +244,5 @@ diagnostics: script: climate_patterns/climate_patterns.py grid: constrained # options: constrained, full imogen_mode: on # options: on, off - output_r2_scores: on # options: on, off parallelise: on # options: on, off area: global # options global, land. If land, uncomment landfrac recipe settings From 08f9397d68d5b66b8dc58acbe2e828ef1dcfe939 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Tue, 13 Feb 2024 15:30:08 +0000 Subject: [PATCH 36/70] Codacy fix --- esmvaltool/diag_scripts/climate_patterns/climate_patterns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index d732ea6ade..2ef43c5eb2 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -53,8 +53,8 @@ rename_variables_base, ) -from esmvaltool.diag_scripts.shared import ProvenanceLogger, run_diagnostic from esmvalcore.preprocessor import area_statistics +from esmvaltool.diag_scripts.shared import ProvenanceLogger, run_diagnostic logger = logging.getLogger(Path(__file__).stem) From e60fa7337750cae14eaa6b3246012b97693686ee Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Tue, 13 Feb 2024 16:14:11 +0000 Subject: [PATCH 37/70] Updated docs and smol fix --- .../climate_patterns/score_timeseries.png | Bin 54711 -> 0 bytes .../source/recipes/recipe_climate_patterns.rst | 12 +----------- .../diag_scripts/climate_patterns/plotting.py | 4 ++-- 3 files changed, 3 insertions(+), 13 deletions(-) delete mode 100644 doc/sphinx/source/recipes/figures/climate_patterns/score_timeseries.png diff --git a/doc/sphinx/source/recipes/figures/climate_patterns/score_timeseries.png b/doc/sphinx/source/recipes/figures/climate_patterns/score_timeseries.png deleted file mode 100644 index a74bbdbc2b457281f6c57b3933ad6d054be72f96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54711 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 nkp92c8T-G{0sQ~xGjBo!S{vud$xa Date: Wed, 10 Apr 2024 10:32:30 +0100 Subject: [PATCH 38/70] Updated reference and citation files --- .zenodo.json | 704 ++++++++++++++++--------------- CITATION.cff | 5 + esmvaltool/config-references.yml | 2 +- 3 files changed, 364 insertions(+), 347 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 083fa51356..0e5d847a1d 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,351 +1,363 @@ { - "creators": [ - { - "affiliation": "NLeSC, Netherlands", - "name": "Andela, Bouwe", - "orcid": "0000-0001-9005-8940" - }, - { - "affiliation": "DLR, Germany", - "name": "Broetz, Bjoern" - }, - { - "affiliation": "PML, UK", - "name": "de Mora, Lee", - "orcid": "0000-0002-5080-3149" - }, - { - "affiliation": "NLeSC, Netherlands", - "name": "Drost, Niels", - "orcid": "0000-0001-9795-7981" - }, - { - "affiliation": "DLR, Germany", - "name": "Eyring, Veronika", - "orcid": "0000-0002-6887-4885" - }, - { - "affiliation": "AWI, Germany", - "name": "Koldunov, Nikolay", - "orcid": "0000-0002-3365-8146" - }, - { - "affiliation": "DLR, Germany", - "name": "Lauer, Axel", - "orcid": "0000-0002-9270-1044" - }, - { - "affiliation": "LMU, Germany", - "name": "Mueller, Benjamin" - }, - { - "affiliation": "URead, UK", - "name": "Predoi, Valeriu", - "orcid": "0000-0002-9729-6578" - }, - { - "affiliation": "DLR, Germany", - "name": "Righi, Mattia", - "orcid": "0000-0003-3827-5950" - }, - { - "affiliation": "DLR, Germany", - "name": "Schlund, Manuel", - "orcid": "0000-0001-5251-0158" - }, - { - "affiliation": "BSC, Spain", - "name": "Vegas-Regidor, Javier", - "orcid": "0000-0003-0096-4291" - }, - { - "affiliation": "SMHI, Sweden", - "name": "Zimmermann, Klaus" - }, - { - "affiliation": "University of Bremen, Germany", - "name": "Adeniyi, Kemisola" - }, - { - "affiliation": "ISAC-CNR, Italy", - "name": "Arnone, Enrico", - "orcid": "0000-0001-6740-5051" - }, - { - "affiliation": "BSC, Spain", - "name": "Bellprat, Omar", - "orcid": "0000-0001-6434-1793" - }, - { - "affiliation": "SMHI, Sweden", - "name": "Berg, Peter", - "orcid": "0000-0002-1469-2568" - }, - { - "affiliation": "DLR, Germany", - "name": "Bock, Lisa", - "orcid": "0000-0001-7058-5938" - }, - { - "affiliation": "BSC, Spain", - "name": "Caron, Louis-Philippe", - "orcid": "0000-0001-5221-0147" - }, - { - "affiliation": "MPI for Biogeochemistry, Germany", - "name": "Carvalhais, Nuno" - }, - { - "affiliation": "ENEA, Italy", - "name": "Cionni, Irene", - "orcid": "0000-0002-0591-9193" - }, - { - "affiliation": "BSC, Spain", - "name": "Cortesi, Nicola", - "orcid": "0000-0002-1442-9225" - }, - { - "affiliation": "ISAC-CNR, Italy", - "name": "Corti, Susanna" - }, - { - "affiliation": "ETH Zurich, Switzerland", - "name": "Crezee, Bas", - "orcid": "0000-0002-1774-1126" - }, - { - "affiliation": "ETH Zurich, Switzerland", - "name": "Davin, Edouard Leopold", - "orcid": "0000-0003-3322-9330" - }, - { - "affiliation": "ISAC-CNR, Italy", - "name": "Davini, Paolo", - "orcid": "0000-0003-3389-7849" - }, - { - "affiliation": "NCAR, USA", - "name": "Deser, Clara" - }, - { - "affiliation": "NLeSC, Netherlands", - "name": "Diblen, Faruk" - }, - { - "affiliation": "UCLouvain, Belgium", - "name": "Docquier, David" - }, - { - "affiliation": "MetOffice, UK", - "name": "Dreyer, Laura" - }, - { - "affiliation": "DKRZ, Germany", - "name": "Ehbrecht, Carsten" - }, - { - "affiliation": "MetOffice, UK", - "name": "Earnshaw, Paul" - }, - { - "affiliation": "University of Bremen, Germany", - "name": "Gier, Bettina" - }, - { - "affiliation": "BSC, Spain", - "name": "Gonzalez-Reviriego, Nube", - "orcid": "0000-0002-5919-6701" - }, - { - "affiliation": "University of Arizona, USA", - "name": "Goodman, Paul" - }, - { - "affiliation": "HZG, Germany", - "name": "Hagemann, Stefan", - "orcid": "0000-0001-5444-2945" - }, - { - "affiliation": "ISAC-CNR, Italy", - "name": "von Hardenberg, Jost", - "orcid": "0000-0002-5312-8070" - }, - { - "affiliation": "DLR, Germany", - "name": "Hassler, Birgit", - "orcid": "0000-0003-2724-709X" - }, - { - "affiliation": "BSC, Spain", - "name": "Hunter, Alasdair", - "orcid": "0000-0001-8365-3709" - }, - { - "affiliation": "FUB, Germany", - "name": "Kadow, Christopher" - }, - { - "affiliation": "DKRZ, Germany", - "name": "Kindermann, Stephan", - "orcid": "0000-0001-9335-1093" - }, - { - "affiliation": "MPI for Biogeochemistry, Germany", - "name": "Koirala, Sujan" - }, - { - "affiliation": "BSC, Spain", - "name": "Lledó, Llorenç" - }, - { - "affiliation": "ETH Zurich, Switzerland", - "name": "Lejeune, Quentin" - }, - { - "affiliation": "University of Hamburg, German", - "name": "Lembo, Valerio", - "orcid": "0000-0001-6085-5914" - }, - { - "affiliation": "MetOffice, UK", - "name": "Little, Bill" - }, - { - "affiliation": "BSC, Spain", - "name": "Loosveldt-Tomas, Saskia" - }, - { - "affiliation": "ETH Zurich, Switzerland", - "name": "Lorenz, Ruth", - "orcid": "0000-0002-3986-1268" - }, - { - "affiliation": "CMCC, Italy", - "name": "Lovato, Tomas", - "orcid": "0000-0002-5188-6767" - }, - { - "affiliation": "University of Hamburg, German", - "name": "Lucarini, Valerio" - }, - { - "affiliation": "UCLouvain, Belgium", - "name": "Massonnet, François" - }, - { - "affiliation": "NIBIO, Norway", - "name": "Mohr, Christian Wilhelm", - "orcid": "0000-0003-2656-1802" - }, - { - "affiliation": "BSC, Spain", - "name": "Moreno-Chamarro, Eduardo" - }, - { - "affiliation": "University of Arizona, USA", - "name": "Amarjiit, Pandde" - }, - { - "affiliation": "BSC, Spain", - "name": "Pérez-Zanón, Núria" - }, - { - "affiliation": "NCAR, USA", - "name": "Phillips, Adam", - "orcid": "0000-0003-4859-8585" - }, - { - "affiliation": "University of Arizona, USA", - "name": "Russell, Joellen" - }, - { - "affiliation": "CICERO, Norway", - "name": "Sandstad, Marit" - }, - { - "affiliation": "MetOffice, UK", - "name": "Sellar, Alistair" - }, - { - "affiliation": "DLR, Germany", - "name": "Senftleben, Daniel" - }, - { - "affiliation": "ISMAR-CNR, Italy", - "name": "Serva, Federico", - "orcid": "0000-0002-7118-0817" - }, - { - "affiliation": "CICERO, Norway", - "name": "Sillmann, Jana" - }, - { - "affiliation": "MPI-M, Germany", - "name": "Stacke, Tobias", - "orcid": "0000-0003-4637-5337" - }, - { - "affiliation": "URead, UK", - "name": "Swaminathan, Ranjini", - "orcid": "0000-0001-5853-2673" - }, - { - "affiliation": "BSC, Spain", - "name": "Torralba, Verónica" - }, - { - "affiliation": "University of Bremen, Germany", - "name": "Weigel, Katja", - "orcid": "0000-0001-6133-7801" - }, - { - "affiliation": "University of Reading, UK", - "name": "Roberts, Charles", - "orcid": "0000-0002-1147-8961" - }, - { - "affiliation": "Netherlands eScience Center", - "name": "Kalverla, Peter", - "orcid": "0000-0002-5025-7862" - }, - { - "affiliation": "Netherlands eScience Center", - "name": "Alidoost, Sarah", - "orcid": "0000-0001-8407-6472" - }, - { - "affiliation": "Netherlands eScience Center", - "name": "Verhoeven, Stefan", - "orcid": "0000-0002-5821-2060" - }, - { - "affiliation": "Netherlands eScience Center", - "name": "Vreede, Barbara", - "orcid": "0000-0002-5023-4601" - }, - { - "affiliation": "Netherlands eScience Center", - "name": "Smeets, Stef", - "orcid": "0000-0002-5413-9038" - }, - { - "affiliation": "Netherlands eScience Center", - "name": "Soares Siqueira, Abel", - "orcid": "0000-0003-4451-281X" - }, - { - "affiliation": "DLR, Germany", - "name": "Kazeroni, Rémi", - "orcid": "0000-0001-7205-9528" - } - ], - "description": "ESMValTool: A community diagnostic and performance metrics tool for routine evaluation of Earth system models in CMIP.", - "license": { - "id": "Apache-2.0" + "creators": [ + { + "affiliation": "NLeSC, Netherlands", + "name": "Andela, Bouwe", + "orcid": "0000-0001-9005-8940" + }, + { + "affiliation": "DLR, Germany", + "name": "Broetz, Bjoern" + }, + { + "affiliation": "PML, UK", + "name": "de Mora, Lee", + "orcid": "0000-0002-5080-3149" + }, + { + "affiliation": "NLeSC, Netherlands", + "name": "Drost, Niels", + "orcid": "0000-0001-9795-7981" + }, + { + "affiliation": "DLR, Germany", + "name": "Eyring, Veronika", + "orcid": "0000-0002-6887-4885" + }, + { + "affiliation": "AWI, Germany", + "name": "Koldunov, Nikolay", + "orcid": "0000-0002-3365-8146" + }, + { + "affiliation": "DLR, Germany", + "name": "Lauer, Axel", + "orcid": "0000-0002-9270-1044" + }, + { + "affiliation": "LMU, Germany", + "name": "Mueller, Benjamin" + }, + { + "affiliation": "URead, UK", + "name": "Predoi, Valeriu", + "orcid": "0000-0002-9729-6578" + }, + { + "affiliation": "DLR, Germany", + "name": "Righi, Mattia", + "orcid": "0000-0003-3827-5950" + }, + { + "affiliation": "DLR, Germany", + "name": "Schlund, Manuel", + "orcid": "0000-0001-5251-0158" + }, + { + "affiliation": "BSC, Spain", + "name": "Vegas-Regidor, Javier", + "orcid": "0000-0003-0096-4291" + }, + { + "affiliation": "SMHI, Sweden", + "name": "Zimmermann, Klaus" + }, + { + "affiliation": "University of Bremen, Germany", + "name": "Adeniyi, Kemisola" + }, + { + "affiliation": "ISAC-CNR, Italy", + "name": "Arnone, Enrico", + "orcid": "0000-0001-6740-5051" + }, + { + "affiliation": "BSC, Spain", + "name": "Bellprat, Omar", + "orcid": "0000-0001-6434-1793" + }, + { + "affiliation": "SMHI, Sweden", + "name": "Berg, Peter", + "orcid": "0000-0002-1469-2568" + }, + { + "affiliation": "DLR, Germany", + "name": "Bock, Lisa", + "orcid": "0000-0001-7058-5938" + }, + { + "affiliation": "BSC, Spain", + "name": "Caron, Louis-Philippe", + "orcid": "0000-0001-5221-0147" + }, + { + "affiliation": "MPI for Biogeochemistry, Germany", + "name": "Carvalhais, Nuno" + }, + { + "affiliation": "ENEA, Italy", + "name": "Cionni, Irene", + "orcid": "0000-0002-0591-9193" + }, + { + "affiliation": "BSC, Spain", + "name": "Cortesi, Nicola", + "orcid": "0000-0002-1442-9225" + }, + { + "affiliation": "ISAC-CNR, Italy", + "name": "Corti, Susanna" + }, + { + "affiliation": "ETH Zurich, Switzerland", + "name": "Crezee, Bas", + "orcid": "0000-0002-1774-1126" + }, + { + "affiliation": "ETH Zurich, Switzerland", + "name": "Davin, Edouard Leopold", + "orcid": "0000-0003-3322-9330" + }, + { + "affiliation": "ISAC-CNR, Italy", + "name": "Davini, Paolo", + "orcid": "0000-0003-3389-7849" + }, + { + "affiliation": "NCAR, USA", + "name": "Deser, Clara" + }, + { + "affiliation": "NLeSC, Netherlands", + "name": "Diblen, Faruk" + }, + { + "affiliation": "UCLouvain, Belgium", + "name": "Docquier, David" + }, + { + "affiliation": "MetOffice, UK", + "name": "Dreyer, Laura" + }, + { + "affiliation": "DKRZ, Germany", + "name": "Ehbrecht, Carsten" + }, + { + "affiliation": "MetOffice, UK", + "name": "Earnshaw, Paul" + }, + { + "affiliation": "University of Bremen, Germany", + "name": "Gier, Bettina" + }, + { + "affiliation": "BSC, Spain", + "name": "Gonzalez-Reviriego, Nube", + "orcid": "0000-0002-5919-6701" + }, + { + "affiliation": "University of Arizona, USA", + "name": "Goodman, Paul" + }, + { + "affiliation": "HZG, Germany", + "name": "Hagemann, Stefan", + "orcid": "0000-0001-5444-2945" + }, + { + "affiliation": "ISAC-CNR, Italy", + "name": "von Hardenberg, Jost", + "orcid": "0000-0002-5312-8070" + }, + { + "affiliation": "DLR, Germany", + "name": "Hassler, Birgit", + "orcid": "0000-0003-2724-709X" + }, + { + "affiliation": "BSC, Spain", + "name": "Hunter, Alasdair", + "orcid": "0000-0001-8365-3709" + }, + { + "affiliation": "FUB, Germany", + "name": "Kadow, Christopher" + }, + { + "affiliation": "DKRZ, Germany", + "name": "Kindermann, Stephan", + "orcid": "0000-0001-9335-1093" + }, + { + "affiliation": "MPI for Biogeochemistry, Germany", + "name": "Koirala, Sujan" + }, + { + "affiliation": "BSC, Spain", + "name": "Lledó, Llorenç" + }, + { + "affiliation": "ETH Zurich, Switzerland", + "name": "Lejeune, Quentin" + }, + { + "affiliation": "University of Hamburg, German", + "name": "Lembo, Valerio", + "orcid": "0000-0001-6085-5914" + }, + { + "affiliation": "MetOffice, UK", + "name": "Little, Bill" + }, + { + "affiliation": "BSC, Spain", + "name": "Loosveldt-Tomas, Saskia" + }, + { + "affiliation": "ETH Zurich, Switzerland", + "name": "Lorenz, Ruth", + "orcid": "0000-0002-3986-1268" + }, + { + "affiliation": "CMCC, Italy", + "name": "Lovato, Tomas", + "orcid": "0000-0002-5188-6767" + }, + { + "affiliation": "University of Hamburg, German", + "name": "Lucarini, Valerio" + }, + { + "affiliation": "UCLouvain, Belgium", + "name": "Massonnet, François" + }, + { + "affiliation": "NIBIO, Norway", + "name": "Mohr, Christian Wilhelm", + "orcid": "0000-0003-2656-1802" + }, + { + "affiliation": "University of Arizona, USA", + "name": "Amarjiit, Pandde" + }, + { + "affiliation": "BSC, Spain", + "name": "Pérez-Zanón, Núria" + }, + { + "affiliation": "NCAR, USA", + "name": "Phillips, Adam", + "orcid": "0000-0003-4859-8585" + }, + { + "affiliation": "University of Arizona, USA", + "name": "Russell, Joellen" + }, + { + "affiliation": "CICERO, Norway", + "name": "Sandstad, Marit" + }, + { + "affiliation": "MetOffice, UK", + "name": "Sellar, Alistair" + }, + { + "affiliation": "DLR, Germany", + "name": "Senftleben, Daniel" + }, + { + "affiliation": "ISMAR-CNR, Italy", + "name": "Serva, Federico", + "orcid": "0000-0002-7118-0817" + }, + { + "affiliation": "CICERO, Norway", + "name": "Sillmann, Jana" + }, + { + "affiliation": "MPI-M, Germany", + "name": "Stacke, Tobias", + "orcid": "0000-0003-4637-5337" + }, + { + "affiliation": "URead, UK", + "name": "Swaminathan, Ranjini", + "orcid": "0000-0001-5853-2673" + }, + { + "affiliation": "BSC, Spain", + "name": "Torralba, Verónica" + }, + { + "affiliation": "University of Bremen, Germany", + "name": "Weigel, Katja", + "orcid": "0000-0001-6133-7801" + }, + { + "affiliation": "University of Reading, UK", + "name": "Roberts, Charles", + "orcid": "0000-0002-1147-8961" + }, + { + "affiliation": "Netherlands eScience Center", + "name": "Kalverla, Peter", + "orcid": "0000-0002-5025-7862" + }, + { + "affiliation": "Netherlands eScience Center", + "name": "Alidoost, Sarah", + "orcid": "0000-0001-8407-6472" + }, + { + "affiliation": "Netherlands eScience Center", + "name": "Verhoeven, Stefan", + "orcid": "0000-0002-5821-2060" + }, + { + "affiliation": "Netherlands eScience Center", + "name": "Vreede, Barbara", + "orcid": "0000-0002-5023-4601" + }, + { + "affiliation": "Netherlands eScience Center", + "name": "Smeets, Stef", + "orcid": "0000-0002-5413-9038" + }, + { + "affiliation": "Netherlands eScience Center", + "name": "Soares Siqueira, Abel", + "orcid": "0000-0003-4451-281X" + }, + { + "affiliation": "DLR, Germany", + "name": "Kazeroni, Rémi", + "orcid": "0000-0001-7205-9528" + }, + { + "affiliation": "NASA, USA", + "name": "Potter, Jerry" + }, + { + "affiliation": "DLR, Germany", + "name": "Winterstein, Franziska", + "orcid": "0000-0002-2406-4936" }, - "title": "ESMValTool", - "communities": [ + { + "affiliation": "MetOffice, UK", + "name": "Munday, Gregory", + "orcid": "0000-0003-4750-9923" + } + ], + "description": "ESMValTool: A community diagnostic and performance metrics tool for routine evaluation of Earth system models in CMIP.", + "license": { + "id": "Apache-2.0" + }, + "publication_date": "2023-07-06", + "title": "ESMValTool", + "version": "v2.9.0", + "communities": [ { "identifier": "is-enes3" }, diff --git a/CITATION.cff b/CITATION.cff index 31404e4c7e..a6ae261b5d 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -349,6 +349,11 @@ authors: family-names: Winterstein given-names: Franziska orcid: "https://orcid.org/0000-0002-2406-4936" + - + affiliation: "MetOffice, UK" + family-names: Munday + given-names: Gregory + orcid: "https://orcid.org/0000-0003-4750-9923" cff-version: 1.2.0 date-released: 2023-07-06 diff --git a/esmvaltool/config-references.yml b/esmvaltool/config-references.yml index 0c98f13ec7..67d801fbe9 100644 --- a/esmvaltool/config-references.yml +++ b/esmvaltool/config-references.yml @@ -379,7 +379,7 @@ authors: munday_gregory: name: Munday, Gregory institute: MetOffice, UK - orcid: + orcid: https://orcid.org/0000-0003-4750-9923 github: mo-gregmunday nikulin_grigory: name: Nikulin, Grigory From fc5bb316a9f86d97198e7f71c113dbbb8d877a33 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Wed, 10 Apr 2024 14:39:27 +0100 Subject: [PATCH 39/70] Addressing some requested changes --- .../source/recipes/recipe_climate_patterns.rst | 13 ++++++------- .../climate_patterns/climate_patterns.py | 1 + .../diag_scripts/climate_patterns/plotting.py | 1 + .../climate_patterns/rename_variables.py | 1 + .../diag_scripts/climate_patterns/sub_functions.py | 3 +-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/sphinx/source/recipes/recipe_climate_patterns.rst b/doc/sphinx/source/recipes/recipe_climate_patterns.rst index c2e571c7ca..f9291c3364 100644 --- a/doc/sphinx/source/recipes/recipe_climate_patterns.rst +++ b/doc/sphinx/source/recipes/recipe_climate_patterns.rst @@ -20,14 +20,14 @@ Available recipes and diagnostics Recipes are stored in esmvaltool/recipes/ - * recipe_climate_patterns.yml +* recipe_climate_patterns.yml Diagnostics are stored in esmvaltool/diag_scripts/climate_patterns/ - * climate_patterns.py: generates climate patterns from input datasets - * rename_variables.py: renames variables depending on user specifications - * sub_functions.py: set of sub functions to assist with driving scripts - * plotting.py: contains all plotting functions for driving scripts +* climate_patterns.py: generates climate patterns from input datasets +* rename_variables.py: renames variables depending on user specifications +* sub_functions.py: set of sub functions to assist with driving scripts +* plotting.py: contains all plotting functions for driving scripts User settings in recipe @@ -44,7 +44,7 @@ User settings in recipe * grid: whether you want to remove Antarctic latitudes or not * imogen_mode: output imogen-specific var names + .nc files * parallelise: parallelise over models or not - * parallel_threads: if you want to paralellise, how many threads you want + * area: calculate the patterns globally, or over land only *Required settings for variables* @@ -72,7 +72,6 @@ Variables * tasmax (atmos, monthly, longitude latitude time) * tasmin (atmos, monthly, longitude latitude time) * tas (atmos, monthly, longitude latitude time) -* hurs (atmos, monthly, longitude latitude time) * huss (atmos, monthly, longitude latitude time) * pr (atmos, monthly, longitude latitude time) * sfcWind (atmos, monthly, longitude latitude time) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 2ef43c5eb2..ca7c21bfdc 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -1,3 +1,4 @@ +# (C) Crown Copyright 2022-2024, Met Office. """Diagnostic script to build climate patterns from CMIP6 models. Description diff --git a/esmvaltool/diag_scripts/climate_patterns/plotting.py b/esmvaltool/diag_scripts/climate_patterns/plotting.py index 907982095c..fd9d5e82a5 100644 --- a/esmvaltool/diag_scripts/climate_patterns/plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/plotting.py @@ -1,3 +1,4 @@ +# (C) Crown Copyright 2022-2024, Met Office. """Script containing plotting functions for driving scripts. Author diff --git a/esmvaltool/diag_scripts/climate_patterns/rename_variables.py b/esmvaltool/diag_scripts/climate_patterns/rename_variables.py index 4bb06319c6..00a7f2611b 100644 --- a/esmvaltool/diag_scripts/climate_patterns/rename_variables.py +++ b/esmvaltool/diag_scripts/climate_patterns/rename_variables.py @@ -1,3 +1,4 @@ +# (C) Crown Copyright 2022-2024, Met Office. """Script containing cube re-naming functions for driving scripts. Author diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 38bc148aea..fc6550463f 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -1,3 +1,4 @@ +# (C) Crown Copyright 2022-2024, Met Office. """Script containing relevant sub-functions for driving scripts. Author @@ -116,7 +117,6 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): ) / 1e12 ) - print("Ocean area: ", ocean_area.data) cube2 = cube.copy() cube2.data = cube2.data * global_weights * ocean_frac.data @@ -138,7 +138,6 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): ) / 1e12 ) - print("Land area: ", land_area.data) cube2 = cube.copy() cube2.data = cube2.data * global_weights * land_frac.data cube2 = ( From 605467fdaffc0c039b3aa2fdc45edd55e3b10742 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Wed, 10 Apr 2024 15:01:19 +0100 Subject: [PATCH 40/70] Added references --- esmvaltool/recipes/recipe_climate_patterns.yml | 4 ++++ .../references/huntingford2000climdyn.bibtex | 14 ++++++++++++++ esmvaltool/references/mathison2024gmd.bibtex | 10 ++++++++++ 3 files changed, 28 insertions(+) create mode 100644 esmvaltool/references/huntingford2000climdyn.bibtex create mode 100644 esmvaltool/references/mathison2024gmd.bibtex diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index 8e67099260..fa13626268 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -11,6 +11,10 @@ documentation: maintainer: - munday_gregory + references: + - mathison2024gmd + - huntingford2000climdyn + preprocessors: global_mean_monthly: monthly_statistics: diff --git a/esmvaltool/references/huntingford2000climdyn.bibtex b/esmvaltool/references/huntingford2000climdyn.bibtex new file mode 100644 index 0000000000..e7aa5bdece --- /dev/null +++ b/esmvaltool/references/huntingford2000climdyn.bibtex @@ -0,0 +1,14 @@ +@article{huntingford2000, + title = {An analogue model to derive additional climate change scenarios from existing {GCM} simulations}, + volume = {16}, + issn = {1432-0894}, + url = {https://doi.org/10.1007/s003820000067}, + doi = {10.1007/s003820000067}, + abstract = {Changes in land surface driving variables, predicted by GCM transient climate change experiments, are confirmed to exhibit linearity in the global mean land temperature anomaly, ΔTl. The associated constants of proportionality retain spatial and seasonal characteristics of the GCM output, whilst ΔTlis related to radiative forcing anomalies. The resultant analogue model is shown to be robust between GCM runs and as such provides a computationally efficient technique of extending existing GCM experiments to a large range of climate change scenarios. As an example impacts study, the analogue model is used to drive a terrestrial ecosystem model, and predicted changes in terrestrial carbon are found to be similar to those when using GCM anomalies directly.}, + number = {8}, + journal = {Climate Dynamics}, + author = {Huntingford, C. and Cox, P. M.}, + month = aug, + year = {2000}, + pages = {575--586}, +} \ No newline at end of file diff --git a/esmvaltool/references/mathison2024gmd.bibtex b/esmvaltool/references/mathison2024gmd.bibtex new file mode 100644 index 0000000000..2a53230950 --- /dev/null +++ b/esmvaltool/references/mathison2024gmd.bibtex @@ -0,0 +1,10 @@ +@Article{mathison2024, +AUTHOR = {Mathison, C. T. and Burke, E. and Kovacs, E. and Munday, G. and Huntingford, C. and Jones, C. and Smith, C. and Steinert, N. and Wiltshire, A. and Gohar, L. and Varney, R.}, +TITLE = {A rapid application emissions-to-impacts tool for scenario assessment: Probabilistic Regional Impacts from Model patterns and Emissions (PRIME)}, +JOURNAL = {EGUsphere}, +VOLUME = {2024}, +YEAR = {2024}, +PAGES = {1--28}, +URL = {https://egusphere.copernicus.org/preprints/2024/egusphere-2023-2932/}, +DOI = {10.5194/egusphere-2023-2932} +} \ No newline at end of file From efa3e0378305e54b335bec6373991568ec910c59 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Wed, 10 Apr 2024 15:10:10 +0100 Subject: [PATCH 41/70] Addressed yamllint output --- esmvaltool/recipes/recipe_climate_patterns.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index fa13626268..26638b2193 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -50,7 +50,7 @@ monthly_global_settings_day: &monthly_global_settings_day # - {dataset: CanESM5, exp: piControl, ensemble: r1i1p1f1, grid: gn} # - {dataset: CanESM5-CanOE, exp: piControl, ensemble: r1i1p2f1, grid: gn} # - {dataset: CanESM5-1, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# # - {dataset: CAS-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use +# # - {dataset: CAS-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use # - {dataset: CESM2, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} # - {dataset: CESM2-WACCM, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} # - {dataset: CMCC-ESM2, exp: piControl, ensemble: r1i1p1f1, grid: gn} @@ -64,7 +64,7 @@ monthly_global_settings_day: &monthly_global_settings_day # - {dataset: EC-Earth3-Veg, exp: piControl, ensemble: r1i1p1f1, grid: gr} # # - {dataset: FGOALS-f3-L, exp: historical, ensemble: r1i1p1f1, grid: gr} # No tasmin/tasmax # - {dataset: FGOALS-g3, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# # - {dataset: FIO-ESM-2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use +# # - {dataset: FIO-ESM-2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use # - {dataset: GFDL-CM4, exp: piControl, ensemble: r1i1p1f1, grid: gr1} # - {dataset: GFDL-ESM4, exp: ssp370, ensemble: r1i1p1f1, grid: gr1} # - {dataset: GISS-E2-1-H, exp: piControl, ensemble: r1i1p1f1, grid: gn} @@ -88,7 +88,7 @@ monthly_global_settings_day: &monthly_global_settings_day # - {dataset: TaiESM1, exp: piControl, ensemble: r1i1p1f1, grid: gn} # - {dataset: UKESM1-0-LL, exp: piControl, ensemble: r1i1p1f2, grid: gn} -CMIP6_no_tasmax: &cmip6_no_tasmax +CMIP6_no_tasmax: &cmip6_no_tasmax # - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2099} # bad tasmin/tasmax # - {dataset: NorESM2-LM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # bad tasmin/tasmax - {dataset: NorESM2-MM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} @@ -246,7 +246,7 @@ diagnostics: scripts: climate_patterns_script: script: climate_patterns/climate_patterns.py - grid: constrained # options: constrained, full - imogen_mode: on # options: on, off - parallelise: on # options: on, off - area: global # options global, land. If land, uncomment landfrac recipe settings + grid: constrained # options: constrained, full + imogen_mode: true # options: on, off + parallelise: true # options: on, off + area: global # options global, land. If land, uncomment landfrac recipe settings From ed97fa2a5e6e2d452b5e3101726761b55074a354 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Wed, 10 Apr 2024 15:37:01 +0100 Subject: [PATCH 42/70] Turned some "real" operations lazy --- .../diag_scripts/climate_patterns/sub_functions.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index fc6550463f..0baa5ee10d 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -63,8 +63,7 @@ def ocean_fraction_calc(sftlf): sftlf.coord("longitude").coord_system = iris.coord_systems.GeogCS( 6371229.0 ) - sftof = sftlf.copy() - sftof.data = 100.0 - sftlf.data + sftof = 100 - sftlf ocean_frac = sftof / 100 land_frac = sftlf / 100 @@ -117,13 +116,12 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): ) / 1e12 ) - cube2 = cube.copy() - cube2.data = cube2.data * global_weights * ocean_frac.data + cube2 = cube * global_weights * ocean_frac cube2 = ( cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / 1e12 - / ocean_area.data + / ocean_area ) if land: @@ -138,12 +136,11 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): ) / 1e12 ) - cube2 = cube.copy() - cube2.data = cube2.data * global_weights * land_frac.data + cube2 = cube * global_weights * land_frac cube2 = ( cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / 1e12 - / land_area.data + / land_area ) if return_cube: From 120cef256fbbd3a6096406943a0e43ebe4524bac Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Wed, 10 Apr 2024 15:56:50 +0100 Subject: [PATCH 43/70] Added lazy operation --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 0baa5ee10d..575a1de349 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -16,6 +16,7 @@ import iris.analysis.cartography import iris.coord_categorisation import numpy as np +import dask as da logger = logging.getLogger(Path(__file__).stem) @@ -105,7 +106,7 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): ) if land is False: - ocean_frac.data = np.ma.masked_less(ocean_frac.data, 0.01) + ocean_frac.data = da.array.ma.masked_less(ocean_frac.core_data(), 0.01) weights = iris.analysis.cartography.area_weights( ocean_frac, normalize=False @@ -125,7 +126,7 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): ) if land: - land_frac.data = np.ma.masked_less(land_frac.data, 0.01) + land_frac.data = da.array.ma.masked_less(land_frac.core_data(), 0.01) weights = iris.analysis.cartography.area_weights( land_frac, normalize=False From edc29b8e8133849d18431efd09e4b7ba2ac8ef21 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Wed, 10 Apr 2024 16:07:47 +0100 Subject: [PATCH 44/70] Removed unnecessary .copy()s --- esmvaltool/diag_scripts/climate_patterns/climate_patterns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index ca7c21bfdc..f7c9410808 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -658,14 +658,14 @@ def patterns(model, cfg): if cfg["area"] == 'land': regressions = calculate_regressions( - anom_list_final.copy(), + anom_list_final, cfg["area"], ocean_frac=ocean_frac, land_frac=land_frac ) if cfg["area"] == 'global': regressions = calculate_regressions( - anom_list_final.copy(), cfg["area"] + anom_list_final, cfg["area"] ) list_of_cubelists = [clim_list_final, anom_list_final, regressions] From 4d2a2ec1e4ee11bc7de4418359ce6058fe7674a2 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 10:36:47 +0100 Subject: [PATCH 45/70] Fixed/updated recipe file for land only runs --- .../climate_patterns/climate_patterns.py | 11 +- .../recipes/recipe_climate_patterns.yml | 112 +++++++++--------- 2 files changed, 58 insertions(+), 65 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index f7c9410808..2c71a20be4 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -606,15 +606,9 @@ def extract_data_from_cfg(cfg, model): cube = constrain_latitude(cube_initial) else: cube = cube_initial - - if dataset["exp"] not in ["historical-ssp585", "ssp585"]: + + if dataset["exp"] != "historical-ssp585": sftlf = cube - elif dataset["exp"] == "ssp585": - # appending to timeseries list - ts_list.append(cube) - # use first year as baseline for anomaly - clim_cube = cube[0] - clim_list.append(clim_cube) else: # appending to timeseries list ts_list.append(cube) @@ -696,6 +690,7 @@ def main(cfg): None """ input_data = cfg["input_data"].values() + print(input_data) parallelise = cfg["parallelise"] models = [] diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index 26638b2193..1b8d0d4ec2 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -42,51 +42,49 @@ monthly_global_settings_day: &monthly_global_settings_day preprocessor: global_mean_monthly -# CMIP6_landfrac: &cmip6_landfrac -# - {dataset: ACCESS-CM2, exp: piControl, ensemble: r1i1p1f1, grid: gn, institute: CSIRO-ARCCSS} -# - {dataset: ACCESS-ESM1-5, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: AWI-CM-1-1-MR, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: BCC-CSM2-MR, exp: hist-resIPO,ensemble: r1i1p1f1, grid: gn} -# - {dataset: CanESM5, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: CanESM5-CanOE, exp: piControl, ensemble: r1i1p2f1, grid: gn} -# - {dataset: CanESM5-1, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# # - {dataset: CAS-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use -# - {dataset: CESM2, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} -# - {dataset: CESM2-WACCM, exp: 1pctCO2, ensemble: r1i1p1f1, grid: gn} -# - {dataset: CMCC-ESM2, exp: piControl, ensemble: r1i1p1f1, grid: gn} +CMIP6_landfrac: &cmip6_landfrac + - {dataset: ACCESS-CM2, exp: piControl, ensemble: r1i1p1f1, grid: gn, institute: CSIRO-ARCCSS} + - {dataset: ACCESS-ESM1-5, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: AWI-CM-1-1-MR, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: BCC-CSM2-MR, exp: hist-resIPO,ensemble: r1i1p1f1, grid: gn} + - {dataset: CanESM5, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: CanESM5-CanOE, exp: piControl, ensemble: r1i1p2f1, grid: gn} + - {dataset: CanESM5-1, exp: piControl, ensemble: r1i1p1f1, grid: gn, institute: CCCma} +# # - {dataset: CAS-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Global only + - {dataset: CMCC-ESM2, exp: piControl, ensemble: r1i1p1f1, grid: gn} # # - {dataset: CMCC-CM2-SR5, exp: piControl, ensemble: r1i1p1f1, grid: gn} # No tasmin/tasmax -# - {dataset: CNRM-CM6-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} -# - {dataset: CNRM-CM6-1-HR, exp: piControl, ensemble: r1i1p1f2, grid: gr} -# - {dataset: CNRM-ESM2-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} + - {dataset: CNRM-CM6-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} + - {dataset: CNRM-CM6-1-HR, exp: piControl, ensemble: r1i1p1f2, grid: gr} + - {dataset: CNRM-ESM2-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} # # - {dataset: E3SM-1-0, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Tasmax == tasmin -# - {dataset: EC-Earth3, exp: piControl, ensemble: r1i1p1f1, grid: gr} -# # - {dataset: EC-Earth3-CC, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use -# - {dataset: EC-Earth3-Veg, exp: piControl, ensemble: r1i1p1f1, grid: gr} + - {dataset: EC-Earth3, exp: piControl, ensemble: r1i1p1f1, grid: gr} +# # - {dataset: EC-Earth3-CC, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Global only + - {dataset: EC-Earth3-Veg, exp: piControl, ensemble: r1i1p1f1, grid: gr} # # - {dataset: FGOALS-f3-L, exp: historical, ensemble: r1i1p1f1, grid: gr} # No tasmin/tasmax -# - {dataset: FGOALS-g3, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# # - {dataset: FIO-ESM-2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Disable land_frac to use -# - {dataset: GFDL-CM4, exp: piControl, ensemble: r1i1p1f1, grid: gr1} -# - {dataset: GFDL-ESM4, exp: ssp370, ensemble: r1i1p1f1, grid: gr1} -# - {dataset: GISS-E2-1-H, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: GISS-E2-1-G, exp: piControl, ensemble: r1i1p5f1, grid: gn} -# - {dataset: GISS-E2-2-G, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: HadGEM3-GC31-LL, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: HadGEM3-GC31-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: INM-CM4-8, exp: piControl, ensemble: r1i1p1f1, grid: gr1} -# - {dataset: INM-CM5-0, exp: abrupt-4xCO2, ensemble: r1i1p1f1, grid: gr1} -# - {dataset: IPSL-CM6A-LR, exp: piControl, ensemble: r1i1p1f1, grid: gr} -# # - {dataset: KACE-1-0-G, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use, weird tasmin/tasmax -# # - {dataset: KIOST-ESM, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Disable land_frac to use -# - {dataset: MIROC6, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: MIROC-ES2L, exp: piControl, ensemble: r1i1p1f2, grid: gn} -# - {dataset: MIROC-ES2H, exp: piControl, ensemble: r1i1p4f2, grid: gn} -# - {dataset: MPI-ESM1-2-HR, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: MPI-ESM1-2-LR, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: MRI-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: NorESM2-LM, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: NorESM2-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: TaiESM1, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# - {dataset: UKESM1-0-LL, exp: piControl, ensemble: r1i1p1f2, grid: gn} + - {dataset: FGOALS-g3, exp: piControl, ensemble: r1i1p1f1, grid: gn} +# # - {dataset: FIO-ESM-2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Global only + - {dataset: GFDL-CM4, exp: piControl, ensemble: r1i1p1f1, grid: gr1} + - {dataset: GFDL-ESM4, exp: ssp370, ensemble: r1i1p1f1, grid: gr1} + - {dataset: GISS-E2-1-H, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: GISS-E2-1-G, exp: piControl, ensemble: r1i1p5f1, grid: gn} + - {dataset: GISS-E2-2-G, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: HadGEM3-GC31-LL, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: HadGEM3-GC31-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: INM-CM4-8, exp: piControl, ensemble: r1i1p1f1, grid: gr1} + - {dataset: INM-CM5-0, exp: abrupt-4xCO2, ensemble: r1i1p1f1, grid: gr1} + - {dataset: IPSL-CM6A-LR, exp: piControl, ensemble: r1i1p1f1, grid: gr} +# # - {dataset: KACE-1-0-G, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Global only, weird tasmin/tasmax +# # - {dataset: KIOST-ESM, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Global only + - {dataset: MIROC6, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: MIROC-ES2L, exp: piControl, ensemble: r1i1p1f2, grid: gn} + - {dataset: MIROC-ES2H, exp: piControl, ensemble: r1i1p4f2, grid: gn} + - {dataset: MPI-ESM1-2-HR, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: MPI-ESM1-2-LR, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: MRI-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} + # - {dataset: NorESM2-LM, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Global only, tasmax == tasmin + - {dataset: NorESM2-MM, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: TaiESM1, exp: piControl, ensemble: r1i1p1f1, grid: gn} + - {dataset: UKESM1-0-LL, exp: piControl, ensemble: r1i1p1f2, grid: gn} CMIP6_no_tasmax: &cmip6_no_tasmax # - {dataset: E3SM-1-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2099} # bad tasmin/tasmax @@ -106,20 +104,20 @@ CMIP6_FULL: &cmip6_full - {dataset: AWI-CM-1-1-MR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: BCC-CSM2-MR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: CanESM5, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - # - {dataset: CanESM5-1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # optional extra + - {dataset: CanESM5-1, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100, institute: CCCma} # optional extra - {dataset: CanESM5-CanOE, exp: [historical, ssp585], ensemble: r1i1p2f1, grid: gn, start_year: 1850, end_year: 2100} - - {dataset: CAS-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: CAS-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # Global only - {dataset: CMCC-ESM2, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # - {dataset: CMCC-CM2-SR5, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # No tasmin/tasmax - {dataset: CNRM-CM6-1, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} - {dataset: CNRM-CM6-1-HR, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} - {dataset: CNRM-ESM2-1, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gr, start_year: 1850, end_year: 2100} - {dataset: EC-Earth3, exp: [historical, ssp585], ensemble: r11i1p1f1, grid: gr, start_year: 1850, end_year: 2100} - - {dataset: EC-Earth3-CC, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} + # - {dataset: EC-Earth3-CC, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} # Global only - {dataset: EC-Earth3-Veg, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} # - {dataset: FGOALS-f3-L, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} # No tasmin/tasmax - # - {dataset: FGOALS-g3, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - - {dataset: FIO-ESM-2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + - {dataset: FGOALS-g3, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} + # - {dataset: FIO-ESM-2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} # Global only - {dataset: GFDL-CM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: GFDL-ESM4, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr1, start_year: 1850, end_year: 2100} - {dataset: GISS-E2-1-H, exp: [historical, ssp585], ensemble: r3i1p1f2, grid: gn, start_year: 1850, end_year: 2100} @@ -134,7 +132,7 @@ CMIP6_FULL: &cmip6_full # - {dataset: KIOST-ESM, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gr, start_year: 1850, end_year: 2100} # optional extra - {dataset: MIROC6, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MIROC-ES2L, exp: [historical, ssp585], ensemble: r1i1p1f2, grid: gn, start_year: 1850, end_year: 2100} - # - {dataset: MIROC-ES2H, exp: [historical, ssp585], ensemble: r1i1p4f2, grid: gn, start_year: 1850, end_year: 2100} # optional extra + - {dataset: MIROC-ES2H, exp: [historical, ssp585], ensemble: r1i1p4f2, grid: gn, start_year: 1850, end_year: 2100} # optional extra - {dataset: MPI-ESM1-2-HR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MPI-ESM1-2-LR, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} - {dataset: MRI-ESM2-0, exp: [historical, ssp585], ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2100} @@ -146,12 +144,12 @@ diagnostics: variables: - # sftlf: - # short_name: sftlf - # mip: fx - # project: CMIP6 - # preprocessor: downscale_sftlf - # additional_datasets: *cmip6_landfrac + sftlf: + short_name: sftlf + mip: fx + project: CMIP6 + preprocessor: downscale_sftlf + additional_datasets: *cmip6_landfrac tasmax_585: short_name: tasmax @@ -247,6 +245,6 @@ diagnostics: climate_patterns_script: script: climate_patterns/climate_patterns.py grid: constrained # options: constrained, full - imogen_mode: true # options: on, off - parallelise: true # options: on, off - area: global # options global, land. If land, uncomment landfrac recipe settings + imogen_mode: true # options: true, false + parallelise: true # options: true, false + area: land # options global, land. If land, uncomment landfrac recipe settings From 8519e565b44c5f0f8d71f3d5ca17a78db0329d13 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 11:36:51 +0100 Subject: [PATCH 46/70] Land warming fix --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 575a1de349..ad5a4f0b4c 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -137,7 +137,9 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): ) / 1e12 ) - cube2 = cube * global_weights * land_frac + + # Iris is too strict so we need to use core_data in this calculation + cube2 = cube * global_weights * land_frac.core_data() cube2 = ( cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) / 1e12 From 4b377ba97bf2979efdb72a4493c8e05947790935 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 12:25:19 +0100 Subject: [PATCH 47/70] Update recipe file --- .../recipes/recipe_climate_patterns.yml | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index 1b8d0d4ec2..ae4cf3e8e9 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -50,19 +50,19 @@ CMIP6_landfrac: &cmip6_landfrac - {dataset: CanESM5, exp: piControl, ensemble: r1i1p1f1, grid: gn} - {dataset: CanESM5-CanOE, exp: piControl, ensemble: r1i1p2f1, grid: gn} - {dataset: CanESM5-1, exp: piControl, ensemble: r1i1p1f1, grid: gn, institute: CCCma} -# # - {dataset: CAS-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Global only + # - {dataset: CAS-ESM2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Global only - {dataset: CMCC-ESM2, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# # - {dataset: CMCC-CM2-SR5, exp: piControl, ensemble: r1i1p1f1, grid: gn} # No tasmin/tasmax + # - {dataset: CMCC-CM2-SR5, exp: piControl, ensemble: r1i1p1f1, grid: gn} # No tasmin/tasmax - {dataset: CNRM-CM6-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} - {dataset: CNRM-CM6-1-HR, exp: piControl, ensemble: r1i1p1f2, grid: gr} - {dataset: CNRM-ESM2-1, exp: piControl, ensemble: r1i1p1f2, grid: gr} -# # - {dataset: E3SM-1-0, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Tasmax == tasmin + # - {dataset: E3SM-1-0, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Tasmax == tasmin - {dataset: EC-Earth3, exp: piControl, ensemble: r1i1p1f1, grid: gr} -# # - {dataset: EC-Earth3-CC, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Global only + # - {dataset: EC-Earth3-CC, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Global only - {dataset: EC-Earth3-Veg, exp: piControl, ensemble: r1i1p1f1, grid: gr} -# # - {dataset: FGOALS-f3-L, exp: historical, ensemble: r1i1p1f1, grid: gr} # No tasmin/tasmax + # - {dataset: FGOALS-f3-L, exp: historical, ensemble: r1i1p1f1, grid: gr} # No tasmin/tasmax - {dataset: FGOALS-g3, exp: piControl, ensemble: r1i1p1f1, grid: gn} -# # - {dataset: FIO-ESM-2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Global only + # - {dataset: FIO-ESM-2-0, exp: piControl, ensemble: r1i1p1f1, grid: gn} # Global only - {dataset: GFDL-CM4, exp: piControl, ensemble: r1i1p1f1, grid: gr1} - {dataset: GFDL-ESM4, exp: ssp370, ensemble: r1i1p1f1, grid: gr1} - {dataset: GISS-E2-1-H, exp: piControl, ensemble: r1i1p1f1, grid: gn} @@ -73,8 +73,8 @@ CMIP6_landfrac: &cmip6_landfrac - {dataset: INM-CM4-8, exp: piControl, ensemble: r1i1p1f1, grid: gr1} - {dataset: INM-CM5-0, exp: abrupt-4xCO2, ensemble: r1i1p1f1, grid: gr1} - {dataset: IPSL-CM6A-LR, exp: piControl, ensemble: r1i1p1f1, grid: gr} -# # - {dataset: KACE-1-0-G, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Global only, weird tasmin/tasmax -# # - {dataset: KIOST-ESM, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Global only + # - {dataset: KACE-1-0-G, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Global only, weird tasmin/tasmax + # - {dataset: KIOST-ESM, exp: piControl, ensemble: r1i1p1f1, grid: gr} # Global only - {dataset: MIROC6, exp: piControl, ensemble: r1i1p1f1, grid: gn} - {dataset: MIROC-ES2L, exp: piControl, ensemble: r1i1p1f2, grid: gn} - {dataset: MIROC-ES2H, exp: piControl, ensemble: r1i1p4f2, grid: gn} @@ -144,12 +144,12 @@ diagnostics: variables: - sftlf: - short_name: sftlf - mip: fx - project: CMIP6 - preprocessor: downscale_sftlf - additional_datasets: *cmip6_landfrac + # sftlf: + # short_name: sftlf + # mip: fx + # project: CMIP6 + # preprocessor: downscale_sftlf + # additional_datasets: *cmip6_landfrac tasmax_585: short_name: tasmax @@ -247,4 +247,4 @@ diagnostics: grid: constrained # options: constrained, full imogen_mode: true # options: true, false parallelise: true # options: true, false - area: land # options global, land. If land, uncomment landfrac recipe settings + area: global # options global, land. If land, uncomment landfrac recipe settings From f8bc37bd403f7cab7aace1da9fdfb643986a2f22 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 12:28:41 +0100 Subject: [PATCH 48/70] Flake8 fixes --- esmvaltool/diag_scripts/climate_patterns/climate_patterns.py | 2 +- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 2c71a20be4..8a6c06683d 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -606,7 +606,7 @@ def extract_data_from_cfg(cfg, model): cube = constrain_latitude(cube_initial) else: cube = cube_initial - + if dataset["exp"] != "historical-ssp585": sftlf = cube else: diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index ad5a4f0b4c..7f9f326caa 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -15,7 +15,6 @@ import iris import iris.analysis.cartography import iris.coord_categorisation -import numpy as np import dask as da logger = logging.getLogger(Path(__file__).stem) @@ -137,8 +136,8 @@ def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): ) / 1e12 ) - - # Iris is too strict so we need to use core_data in this calculation + + # Iris is too strict so we need to use core_data in this calculation cube2 = cube * global_weights * land_frac.core_data() cube2 = ( cube2.collapsed(["latitude", "longitude"], iris.analysis.SUM) From e1a263bb4cee23bbe18a97fe792bc371f75bf493 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 12:58:52 +0100 Subject: [PATCH 49/70] Review fixes --- .../climate_patterns/climate_patterns.py | 48 ++++++++++++------- .../diag_scripts/climate_patterns/plotting.py | 16 ++++--- .../climate_patterns/sub_functions.py | 23 ++++----- .../recipes/recipe_climate_patterns.yml | 1 - .../references/huntingford2000climdyn.bibtex | 2 +- esmvaltool/references/mathison2024gmd.bibtex | 18 +++---- 6 files changed, 60 insertions(+), 48 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 8a6c06683d..564f30770a 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -18,12 +18,12 @@ grid: str, optional (default: constrained) options: constrained, full def: removes Antarctica from grid -imogen_mode: bool, optional (default: off) - options: on, off +jules_mode: bool, optional (default: true) + options: true, false def: outputs extra data (anomaly, climatology) per variable to drive JULES-IMOGEN configuration -parallelise: bool, optional (default: off) - options: on, off +parallelise: bool, optional (default: true) + options: true, false def: parallelises code to run N models at once parallel_threads: int, optional (default: null) options: any int, up to the amount of CPU cores accessible by user @@ -34,6 +34,7 @@ import logging from pathlib import Path +import os import iris import iris.coord_categorisation @@ -479,7 +480,7 @@ def calculate_regressions( return regr_var_list -def cube_saver(list_of_cubelists, work_path, name_list, mode): +def cube_saver(list_of_cubelists, work_path, name_list, jules_mode): """Save desired cubelists to work_dir, depending on switch settings. Parameters @@ -497,14 +498,19 @@ def cube_saver(list_of_cubelists, work_path, name_list, mode): ------- None """ - if mode == "imogen": + if jules_mode: for i in range(0, 3): - iris.save(list_of_cubelists[i], work_path + name_list[i]) - - if mode == "base": - for cube in list_of_cubelists[2]: - rename_variables_base(cube) - iris.save(list_of_cubelists[2], work_path + name_list[2]) + iris.save( + list_of_cubelists[i], + os.path.join(work_path, name_list[i]) + ) + else: + for i, cube in enumerate(list_of_cubelists[2]): + list_of_cubelists[2][i] = rename_variables_base(cube) + iris.save( + list_of_cubelists[2], + os.path.join(work_path, name_list[2]) + ) def save_outputs( @@ -538,15 +544,25 @@ def save_outputs( ] # saving data + plotting - if cfg["imogen_mode"] is True: + if cfg["jules_mode"] is True: plot_climatologies_timeseries(list_of_cubelists[0], plot_path) plot_anomalies_timeseries(list_of_cubelists[1], plot_path) plot_patterns_timeseries(list_of_cubelists[2], plot_path) - cube_saver(list_of_cubelists, work_path, name_list, mode="imogen") + cube_saver( + list_of_cubelists, + work_path, + name_list, + jules_mode=cfg["jules_mode"] + ) else: plot_patterns(list_of_cubelists[2], plot_path) - cube_saver(list_of_cubelists, work_path, name_list, mode="base") + cube_saver( + list_of_cubelists, + work_path, + name_list, + jules_mode=cfg["jules_mode"] + ) def get_provenance_record(): @@ -672,7 +688,7 @@ def patterns(model, cfg): ) provenance_record = get_provenance_record() - path = model_work_dir + "patterns.nc" + path = os.path.join(model_work_dir, "patterns.nc") with ProvenanceLogger(cfg) as provenance_logger: provenance_logger.log(path, provenance_record) diff --git a/esmvaltool/diag_scripts/climate_patterns/plotting.py b/esmvaltool/diag_scripts/climate_patterns/plotting.py index fd9d5e82a5..8711e2864d 100644 --- a/esmvaltool/diag_scripts/climate_patterns/plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/plotting.py @@ -5,6 +5,8 @@ ------ Gregory Munday (Met Office, UK) """ +import os + import iris.quickplot as qplt import matplotlib.pyplot as plt import numpy as np @@ -41,7 +43,7 @@ def subplot_positions(j): def plot_patterns(cube_list, plot_path): - """Plot climate patterns for imogen_mode: off. + """Plot climate patterns for jules_mode: off. Parameters ---------- @@ -77,11 +79,11 @@ def plot_patterns(cube_list, plot_path): qplt.pcolormesh(cube[0]) plt.tight_layout() - plt.savefig(plot_path + "Patterns") + plt.savefig(os.path.join(plot_path, "Patterns"), dpi=300) plt.close() fig.tight_layout() - fig.savefig(plot_path + "Patterns Timeseries") + fig.savefig(os.path.join(plot_path, "Patterns Timeseries"), dpi=300) def plot_patterns_timeseries(cubelist, plot_path): @@ -121,10 +123,10 @@ def plot_patterns_timeseries(cubelist, plot_path): qplt.pcolormesh(cube[0]) fig.tight_layout() - fig.savefig(plot_path + "Patterns Timeseries") + fig.savefig(os.path.join(plot_path, "Patterns Timeseries"), dpi=300) plt.tight_layout() - plt.savefig(plot_path + "Patterns") + plt.savefig(os.path.join(plot_path, "Patterns"), dpi=300) plt.close() @@ -159,7 +161,7 @@ def plot_anomalies_timeseries(cubelist, plot_path): axs[x_pos, y_pos].set_xlabel("Time") fig.tight_layout() - fig.savefig(plot_path + "Anomalies") + fig.savefig(os.path.join(plot_path, "Anomalies"), dpi=300) def plot_climatologies_timeseries(cubelist, plot_path): @@ -192,4 +194,4 @@ def plot_climatologies_timeseries(cubelist, plot_path): axs[x_pos, y_pos].set_xlabel("Time") fig.tight_layout() - fig.savefig(plot_path + "Climatologies") + fig.savefig(os.path.join(plot_path, "Climatologies"), dpi=300) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 7f9f326caa..2447160efd 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -54,8 +54,6 @@ def ocean_fraction_calc(sftlf): ocean_fraction cube for area-weights land_frac: cube land_fraction cube for area-weights - of: float - ocean fraction float for EBM calculations """ sftlf.coord("latitude").coord_system = iris.coord_systems.GeogCS( 6371229.0 @@ -168,18 +166,15 @@ def make_model_dirs(cfg, model): model_plot_dir : path path to specific plot directory in plot_dir """ - work_path = cfg["work_dir"] + "/" - plot_path = cfg["plot_dir"] + "/" - w_path = os.path.join(work_path, model) - p_path = os.path.join(plot_path, model) - - if not os.path.exists(w_path): - os.mkdir(w_path) - if not os.path.exists(p_path): - os.mkdir(p_path) - - model_work_dir = w_path + "/" - model_plot_dir = p_path + "/" + work_path = cfg["work_dir"] + plot_path = cfg["plot_dir"] + model_work_dir = os.path.join(work_path, model) + model_plot_dir = os.path.join(plot_path, model) + + if not os.path.exists(model_work_dir): + os.mkdir(model_work_dir) + if not os.path.exists(model_plot_dir): + os.mkdir(model_plot_dir) return model_work_dir, model_plot_dir diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index ae4cf3e8e9..9fa14604f3 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -245,6 +245,5 @@ diagnostics: climate_patterns_script: script: climate_patterns/climate_patterns.py grid: constrained # options: constrained, full - imogen_mode: true # options: true, false parallelise: true # options: true, false area: global # options global, land. If land, uncomment landfrac recipe settings diff --git a/esmvaltool/references/huntingford2000climdyn.bibtex b/esmvaltool/references/huntingford2000climdyn.bibtex index e7aa5bdece..69bc072d49 100644 --- a/esmvaltool/references/huntingford2000climdyn.bibtex +++ b/esmvaltool/references/huntingford2000climdyn.bibtex @@ -11,4 +11,4 @@ month = aug, year = {2000}, pages = {575--586}, -} \ No newline at end of file +} diff --git a/esmvaltool/references/mathison2024gmd.bibtex b/esmvaltool/references/mathison2024gmd.bibtex index 2a53230950..a6090db6c7 100644 --- a/esmvaltool/references/mathison2024gmd.bibtex +++ b/esmvaltool/references/mathison2024gmd.bibtex @@ -1,10 +1,10 @@ @Article{mathison2024, -AUTHOR = {Mathison, C. T. and Burke, E. and Kovacs, E. and Munday, G. and Huntingford, C. and Jones, C. and Smith, C. and Steinert, N. and Wiltshire, A. and Gohar, L. and Varney, R.}, -TITLE = {A rapid application emissions-to-impacts tool for scenario assessment: Probabilistic Regional Impacts from Model patterns and Emissions (PRIME)}, -JOURNAL = {EGUsphere}, -VOLUME = {2024}, -YEAR = {2024}, -PAGES = {1--28}, -URL = {https://egusphere.copernicus.org/preprints/2024/egusphere-2023-2932/}, -DOI = {10.5194/egusphere-2023-2932} -} \ No newline at end of file + AUTHOR = {Mathison, C. T. and Burke, E. and Kovacs, E. and Munday, G. and Huntingford, C. and Jones, C. and Smith, C. and Steinert, N. and Wiltshire, A. and Gohar, L. and Varney, R.}, + TITLE = {A rapid application emissions-to-impacts tool for scenario assessment: Probabilistic Regional Impacts from Model patterns and Emissions (PRIME)}, + JOURNAL = {EGUsphere}, + VOLUME = {2024}, + YEAR = {2024}, + PAGES = {1--28}, + URL = {https://egusphere.copernicus.org/preprints/2024/egusphere-2023-2932/}, + DOI = {10.5194/egusphere-2023-2932} +} From d6d4bc691f89c539e5c989fd062c2ef190eab5c3 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 13:06:34 +0100 Subject: [PATCH 50/70] Added reference --- doc/sphinx/source/recipes/recipe_climate_patterns.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/sphinx/source/recipes/recipe_climate_patterns.rst b/doc/sphinx/source/recipes/recipe_climate_patterns.rst index f9291c3364..c74626f5ad 100644 --- a/doc/sphinx/source/recipes/recipe_climate_patterns.rst +++ b/doc/sphinx/source/recipes/recipe_climate_patterns.rst @@ -92,6 +92,11 @@ References change scenarios from existing GCM simulations. Climate Dynamics 16, 575–586 (2000). https://doi.org/10.1007/s003820000067 +* Mathison, C. T. et al. A rapid application emissions-to-impacts tool + for scenario assessment: Probabilistic Regional Impacts from Model patterns + and Emissions (PRIME). + EGUsphere [preprint], (2024). https://doi.org/10.5194/egusphere-2023-2932 + Example plots ------------- From 621f561c0621dc450088e162ff09080ca80f4ba3 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 13:09:07 +0100 Subject: [PATCH 51/70] imogen_mode --> jules_mode --- esmvaltool/recipes/recipe_climate_patterns.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index 9fa14604f3..fe9845d0c4 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -245,5 +245,6 @@ diagnostics: climate_patterns_script: script: climate_patterns/climate_patterns.py grid: constrained # options: constrained, full + jules_mode: true # options: true, false parallelise: true # options: true, false area: global # options global, land. If land, uncomment landfrac recipe settings From 6aacddf464bb000aff63dd2398f9c086a640b6a3 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 13:52:53 +0100 Subject: [PATCH 52/70] Updated switches + doc --- .../climate_patterns/climate_patterns.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 564f30770a..cfbe8f1120 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -18,18 +18,16 @@ grid: str, optional (default: constrained) options: constrained, full def: removes Antarctica from grid -jules_mode: bool, optional (default: true) +jules_mode: bool, optional (default: false) options: true, false def: outputs extra data (anomaly, climatology) per variable to drive JULES-IMOGEN configuration -parallelise: bool, optional (default: true) +parallelise: bool, optional (default: false) options: true, false def: parallelises code to run N models at once -parallel_threads: int, optional (default: null) - options: any int, up to the amount of CPU cores accessible by user - def: number of threads/cores to parallelise over. If 'parallelise: on' and - 'parallel_threads': null, diagnostic will automatically parallelise - over N-1 accessible threads +area: str, optional (default: global) + options: global, land + def: area over which to calculate climate patterns """ import logging @@ -673,7 +671,7 @@ def patterns(model, cfg): ocean_frac=ocean_frac, land_frac=land_frac ) - if cfg["area"] == 'global': + else: regressions = calculate_regressions( anom_list_final, cfg["area"] ) From 0c3ae70f5167aab4cf8b87e006c4b38d4aa08b59 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 15:06:28 +0100 Subject: [PATCH 53/70] Updated with preprocessors --- .../climate_patterns/climate_patterns.py | 86 ++++--------------- .../recipes/recipe_climate_patterns.yml | 1 - 2 files changed, 17 insertions(+), 70 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index cfbe8f1120..7c8a7e0b4e 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -15,9 +15,6 @@ Configuration options in recipe ------------------------------- -grid: str, optional (default: constrained) - options: constrained, full - def: removes Antarctica from grid jules_mode: bool, optional (default: false) options: true, false def: outputs extra data (anomaly, climatology) per variable @@ -53,13 +50,17 @@ rename_variables_base, ) -from esmvalcore.preprocessor import area_statistics +from esmvalcore.preprocessor import ( + area_statistics, + extract_time, + climate_statistics +) from esmvaltool.diag_scripts.shared import ProvenanceLogger, run_diagnostic logger = logging.getLogger(Path(__file__).stem) -def climatology(cube, syr=1850, eyr=1889): +def calculate_climatology(cube, syr=1850, eyr=1889): """Handle aggregation to make climatology. Parameters @@ -76,68 +77,20 @@ def climatology(cube, syr=1850, eyr=1889): cube_aggregated : cube 40 year climatology cube from syr-eyr (default 1850-1889) """ - cube_40yr = cube.extract( - iris.Constraint( - time=lambda t: syr <= t.point.year <= eyr, - month_number=lambda t: 1 <= t.point <= 12, - ) + cube_40yr = extract_time( + cube, + start_year=syr, + start_month=1, + start_day=1, + end_year=eyr, + end_month=12, + end_day=31 ) - cube_aggregated = make_monthly_climatology(cube_40yr) + cube_aggregated = climate_statistics(cube_40yr, 'mean', 'month') return cube_aggregated -def constrain_latitude(cube, min_lat=-55, max_lat=82.5): - """Constrains latitude to decrease run-time when output fed to IMOGEN. - - Parameters - ---------- - cube : cube - cube loaded from config dictionary - min_lat : float - minimum latitude to crop - max_lat : float - maximum latitude to crop - - Returns - ------- - cube_clipped : cube - cube with latitudes set from -55 to 82.5 degrees - """ - cube_clipped = cube.extract( - iris.Constraint(latitude=lambda cell: max_lat >= cell >= min_lat) - ) - - return cube_clipped - - -def make_monthly_climatology(cube): - """Generate a climatology by month_number. - - Parameters - ---------- - cube : cube - cube loaded from config dictionary - - Returns - ------- - cube_month_climatol : cube - cube aggregated by month_number - """ - if not cube.coords("month_number"): - iris.coord_categorisation.add_month_number( - cube, - "time", - "month_number" - ) - cube_month_climatol = cube.aggregated_by( - "month_number", - iris.analysis.MEAN - ) - - return cube_month_climatol - - def diurnal_temp_range(cubelist): """Calculate diurnal range from monthly max and min temperatures. @@ -614,12 +567,7 @@ def extract_data_from_cfg(cfg, model): input_file = dataset["filename"] # preparing single cube - cube_initial = sf.load_cube(input_file) - - if cfg["grid"] == "constrained": - cube = constrain_latitude(cube_initial) - else: - cube = cube_initial + cube = sf.load_cube(input_file) if dataset["exp"] != "historical-ssp585": sftlf = cube @@ -628,7 +576,7 @@ def extract_data_from_cfg(cfg, model): ts_list.append(cube) # making climatology - clim_cube = climatology(cube) + clim_cube = calculate_climatology(cube) clim_list.append(clim_cube) if cfg["area"] == 'land': diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index fe9845d0c4..dde7515491 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -244,7 +244,6 @@ diagnostics: scripts: climate_patterns_script: script: climate_patterns/climate_patterns.py - grid: constrained # options: constrained, full jules_mode: true # options: true, false parallelise: true # options: true, false area: global # options global, land. If land, uncomment landfrac recipe settings From 1c6131211010e1bfd222c81646db8636311cc494 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 15:09:01 +0100 Subject: [PATCH 54/70] Updated docs --- doc/sphinx/source/recipes/recipe_climate_patterns.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/sphinx/source/recipes/recipe_climate_patterns.rst b/doc/sphinx/source/recipes/recipe_climate_patterns.rst index c74626f5ad..140ab78ff3 100644 --- a/doc/sphinx/source/recipes/recipe_climate_patterns.rst +++ b/doc/sphinx/source/recipes/recipe_climate_patterns.rst @@ -11,7 +11,7 @@ datasets. .. note:: The regrid setting in the recipe is set to a 2.5x3.75 grid. This is done to - match the current resolution in the IMOGEN-JULES framework, but can be + match the current resolution in the IMOGEN-JULES model, but can be adjusted with no issues for a finer/coarser patterns grid. @@ -41,8 +41,7 @@ User settings in recipe *Optional settings for script* - * grid: whether you want to remove Antarctic latitudes or not - * imogen_mode: output imogen-specific var names + .nc files + * jules_mode: output jules-specific var names + .nc files * parallelise: parallelise over models or not * area: calculate the patterns globally, or over land only From d560d6ef10764b2e5d33030bbd03c7da9d97c471 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 15:26:18 +0100 Subject: [PATCH 55/70] Prov update --- .../climate_patterns/climate_patterns.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 7c8a7e0b4e..490ac699f8 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -628,15 +628,17 @@ def patterns(model, cfg): save_outputs(cfg, list_of_cubelists, model) - model_work_dir, _ = sf.make_model_dirs( - cfg, - model - ) - - provenance_record = get_provenance_record() - path = os.path.join(model_work_dir, "patterns.nc") - with ProvenanceLogger(cfg) as provenance_logger: - provenance_logger.log(path, provenance_record) + ### Provenance Logging, removed due to sporadic errors. Fix later. + + # model_work_dir, _ = sf.make_model_dirs( + # cfg, + # model + # ) + + # provenance_record = get_provenance_record() + # path = os.path.join(model_work_dir, "patterns.nc") + # with ProvenanceLogger(cfg) as provenance_logger: + # provenance_logger.log(path, provenance_record) def main(cfg): From 365784403dd2ea3a3e09b56b711eb82cca750bf3 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 15:30:38 +0100 Subject: [PATCH 56/70] Flake8 --- .../diag_scripts/climate_patterns/climate_patterns.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 490ac699f8..20b2cfd9d7 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -55,7 +55,7 @@ extract_time, climate_statistics ) -from esmvaltool.diag_scripts.shared import ProvenanceLogger, run_diagnostic +from esmvaltool.diag_scripts.shared import run_diagnostic logger = logging.getLogger(Path(__file__).stem) @@ -442,7 +442,7 @@ def cube_saver(list_of_cubelists, work_path, name_list, jules_mode): path to work_dir, to save cubelists name_list : list list of filename strings for saving - mode : str + jules_mode : str switch option passed through by ESMValTool config dict Returns @@ -628,8 +628,8 @@ def patterns(model, cfg): save_outputs(cfg, list_of_cubelists, model) - ### Provenance Logging, removed due to sporadic errors. Fix later. - + # Provenance Logging, removed due to sporadic errors. Fix later. + # model_work_dir, _ = sf.make_model_dirs( # cfg, # model From cfd3e53610e70f245652c1deb09c1a655ae031b0 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 16:23:40 +0100 Subject: [PATCH 57/70] Reducing local vars... --- .../climate_patterns/climate_patterns.py | 54 ++++++------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 20b2cfd9d7..552ac57b86 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -263,8 +263,7 @@ def regression(tas, cube_data, area, ocean_frac=None, land_frac=None): tas_data = sf.area_avg_landsea( tas, ocean_frac, land_frac, land=True, return_cube=False ) - - if area == 'global': + else: # calculate global average warming tas_data = area_statistics(tas, 'mean').data @@ -286,26 +285,6 @@ def regression(tas, cube_data, area, ocean_frac=None, land_frac=None): return slope_array -def regression_units(tas, cube): - """Calculate regression coefficient units. - - Parameters - ---------- - tas : cube - near-surface air temperature - cube : cube - cube of a given variable - - Returns - ------- - units : str - string of calculated regression units - """ - units = cube.units / tas.units - - return units - - def create_cube(tas_cube, ssp_cube, array, month_number, units=None): """Create a new cube from existing metadata, and new aray data. @@ -382,51 +361,48 @@ def calculate_regressions( cube list of newly created regression slope value cubes, for each var """ regr_var_list = iris.cube.CubeList([]) - months = yrs * 12 for cube in anom_list: if cube.var_name == "tl1_anom": # convert years to months when selecting - tas = cube[-months:] + tas = cube[-yrs*12:] for cube in anom_list: - cube_ssp = cube[-months:] + cube = cube[-yrs*12:] month_list = iris.cube.CubeList([]) # extracting months, regressing, and merging for i in range(1, 13): - month_constraint = iris.Constraint(imogen_drive=i) - month_cube_ssp = cube_ssp.extract(month_constraint) - month_tas = tas.extract(month_constraint) + month_cube = cube.extract(iris.Constraint(imogen_drive=i)) + month_tas = tas.extract(iris.Constraint(imogen_drive=i)) if area == 'land': regr_array = regression( month_tas, - month_cube_ssp.data, + month_cube.data, area=area, ocean_frac=ocean_frac, land_frac=land_frac, ) - - if area == 'global': + else: regr_array = regression( month_tas, - month_cube_ssp.data, + month_cube.data, area=area, ) if cube.var_name in ("swdown_anom", "lwdown_anom"): units = "W m-2 K-1" else: - units = regression_units(tas, cube_ssp) + units = cube.units / tas.units - # creating cube of regression values - regr_cube = create_cube(tas, cube_ssp, regr_array, i, units=units) - - month_list.append(regr_cube) + # create, and append cube of regression values + month_list.append( + create_cube(tas, cube, regr_array, i, units=units) + ) - conc_cube = month_list.merge_cube() - regr_var_list.append(conc_cube) + month_list = month_list.merge_cube() + regr_var_list.append(month_list) return regr_var_list From e7e6c213e440045f349ec9e51f3e23eaab2a81c0 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 16:27:27 +0100 Subject: [PATCH 58/70] Codacy style fix --- esmvaltool/diag_scripts/climate_patterns/climate_patterns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 552ac57b86..3c5c7e13b7 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -365,10 +365,10 @@ def calculate_regressions( for cube in anom_list: if cube.var_name == "tl1_anom": # convert years to months when selecting - tas = cube[-yrs*12:] + tas = cube[-yrs * 12:] for cube in anom_list: - cube = cube[-yrs*12:] + cube = cube[-yrs * 12:] month_list = iris.cube.CubeList([]) # extracting months, regressing, and merging From 4d0b9bac978076c1088a357ce9bcfbeb7e09bb26 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 17:39:27 +0100 Subject: [PATCH 59/70] Refactored rename_variables.py --- .../recipes/recipe_climate_patterns.rst | 1 - .../climate_patterns/climate_patterns.py | 22 +- .../climate_patterns/rename_variables.py | 232 ------------------ .../climate_patterns/sub_functions.py | 51 ++++ 4 files changed, 62 insertions(+), 244 deletions(-) delete mode 100644 esmvaltool/diag_scripts/climate_patterns/rename_variables.py diff --git a/doc/sphinx/source/recipes/recipe_climate_patterns.rst b/doc/sphinx/source/recipes/recipe_climate_patterns.rst index 140ab78ff3..f7336c91c4 100644 --- a/doc/sphinx/source/recipes/recipe_climate_patterns.rst +++ b/doc/sphinx/source/recipes/recipe_climate_patterns.rst @@ -25,7 +25,6 @@ Recipes are stored in esmvaltool/recipes/ Diagnostics are stored in esmvaltool/diag_scripts/climate_patterns/ * climate_patterns.py: generates climate patterns from input datasets -* rename_variables.py: renames variables depending on user specifications * sub_functions.py: set of sub functions to assist with driving scripts * plotting.py: contains all plotting functions for driving scripts diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 3c5c7e13b7..31267d6319 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -36,6 +36,7 @@ import iris.cube import numpy as np import sklearn.linear_model + import sub_functions as sf from plotting import ( plot_patterns_timeseries, @@ -43,13 +44,6 @@ plot_climatologies_timeseries, plot_patterns ) -from rename_variables import ( - rename_anom_variables, - rename_clim_variables, - rename_regression_variables, - rename_variables_base, -) - from esmvalcore.preprocessor import ( area_statistics, extract_time, @@ -316,7 +310,7 @@ def create_cube(tas_cube, ssp_cube, array, month_number, units=None): coord_month = iris.coords.AuxCoord(month_number, var_name="imogen_drive") aux_coords_and_dims = [(coord_month, ())] - cube = rename_regression_variables(ssp_cube) + cube = sf.rename_variables(ssp_cube, has_orig_vars=False) # creating cube cube = iris.cube.Cube( @@ -433,7 +427,9 @@ def cube_saver(list_of_cubelists, work_path, name_list, jules_mode): ) else: for i, cube in enumerate(list_of_cubelists[2]): - list_of_cubelists[2][i] = rename_variables_base(cube) + list_of_cubelists[2][i] = sf.rename_variables( + cube, has_orig_vars=False + ) iris.save( list_of_cubelists[2], os.path.join(work_path, name_list[2]) @@ -585,8 +581,12 @@ def patterns(model, cfg): clim_list_final, anom_list_final = calculate_anomaly(clim_list, ts_list) for i, cube in enumerate(clim_list_final): - rename_clim_variables(cube) - rename_anom_variables(anom_list_final[i]) + sf.rename_variables( + cube, has_orig_vars=True, new_extension="_clim" + ) + sf.rename_variables( + anom_list_final[i], has_orig_vars=True, new_extension="_anom" + ) if cfg["area"] == 'land': regressions = calculate_regressions( diff --git a/esmvaltool/diag_scripts/climate_patterns/rename_variables.py b/esmvaltool/diag_scripts/climate_patterns/rename_variables.py deleted file mode 100644 index 00a7f2611b..0000000000 --- a/esmvaltool/diag_scripts/climate_patterns/rename_variables.py +++ /dev/null @@ -1,232 +0,0 @@ -# (C) Crown Copyright 2022-2024, Met Office. -"""Script containing cube re-naming functions for driving scripts. - -Author ------- -Gregory Munday (Met Office, UK) -""" - - -def rename_clim_variables(cube): - """Rename variables and a coord to fit in JULES framework. - - Parameters - ---------- - cube : cube - input cube - - Returns - ------- - cube : cube - cube with renamed variables - """ - if cube.var_name == "tas": - cube.rename("Air Temperature") - cube.var_name = "tl1_clim" - if cube.var_name == "range_tl1": - cube.rename("Diurnal Range") - cube.var_name = "range_tl1_clim" - if cube.var_name == "hurs": - cube.rename("Relative Humidity") - cube.var_name = "rh1p5m_clim" - if cube.var_name == "huss": - cube.rename("Specific Humidity") - cube.var_name = "ql1_clim" - if cube.var_name == "pr": - cube.rename("Precipitation") - cube.var_name = "precip_clim" - if cube.var_name == "sfcWind": - cube.rename("Wind Speed") - cube.var_name = "wind_clim" - if cube.var_name == "ps": - cube.rename("Surface Pressure") - cube.var_name = "pstar_clim" - if cube.var_name == "rsds": - cube.rename("Surface Downwelling Shortwave Radiation") - cube.var_name = "swdown_clim" - if cube.var_name == "rlds": - cube.rename("Surface Downwelling Longwave Radiation") - cube.var_name = "lwdown_clim" - - cube.coord("month_number").rename("imogen_drive") - - return cube - - -def rename_anom_variables(cube): - """Rename variables and a coord to fit in JULES framework. - - Parameters - ---------- - cube : cube - input cube - - Returns - ------- - cube : cube - cube with renamed variables - """ - if cube.var_name == "tas": - cube.rename("Air Temperature") - cube.var_name = "tl1_anom" - if cube.var_name == "range_tl1": - cube.rename("Diurnal Range") - cube.var_name = "range_tl1_anom" - if cube.var_name == "hurs": - cube.rename("Relative Humidity") - cube.var_name = "rh1p5m_anom" - if cube.var_name == "huss": - cube.rename("Specific Humidity") - cube.var_name = "ql1_anom" - if cube.var_name == "pr": - cube.rename("Precipitation") - cube.var_name = "precip_anom" - if cube.var_name == "sfcWind": - cube.rename("Wind Speed") - cube.var_name = "wind_anom" - if cube.var_name == "ps": - cube.rename("Surface Pressure") - cube.var_name = "pstar_anom" - if cube.var_name == "rsds": - cube.rename("Surface Downwelling Shortwave Radiation") - cube.var_name = "swdown_anom" - if cube.var_name == "rlds": - cube.rename("Surface Downwelling Longwave Radiation") - cube.var_name = "lwdown_anom" - - cube.coord("month_number").rename("imogen_drive") - - return cube - - -def rename_variables(cube): - """Rename variables and a coord to fit in JULES framework. - - Parameters - ---------- - cube : cube - input cube - - Returns - ------- - cube : cube - cube with renamed variables - """ - if cube.var_name == "tas": - cube.rename("Air Temperature") - cube.var_name = "tl1" - if cube.var_name == "hurs": - cube.rename("Relative Humidity") - cube.var_name = "rh1p5m" - if cube.var_name == "huss": - cube.rename("Specific Humidity") - cube.var_name = "ql1" - if cube.var_name == "pr": - cube.rename("Precipitation") - cube.var_name = "precip" - if cube.var_name == "sfcWind": - cube.rename("Wind Speed") - cube.var_name = "wind" - if cube.var_name == "ps": - cube.rename("Surface Pressure") - cube.var_name = "pstar" - if cube.var_name == "rsds": - cube.rename("Surface Downwelling Shortwave Radiation") - cube.var_name = "swdown" - if cube.var_name == "rlds": - cube.rename("Surface Downwelling Longwave Radiation") - cube.var_name = "lwdown" - - cube.coord("month_number").rename("imogen_drive") - - return cube - - -def rename_regression_variables(cube): - """Rename variables to fit in JULES framework. - - Parameters - ---------- - cube : cube - input cube - - Returns - ------- - cube : cube - cube with renamed variables - """ - if cube.var_name == "tl1_anom": - cube.rename("Air Temperature") - cube.var_name = "tl1_patt" - if cube.var_name == "range_tl1_anom": - cube.rename("Diurnal Range") - cube.var_name = "range_tl1_patt" - if cube.var_name == "rh1p5m_anom": - cube.rename("Relative Humidity") - cube.var_name = "rh1p5m_patt" - if cube.var_name == "ql1_anom": - cube.rename("Specific Humidity") - cube.var_name = "ql1_patt" - if cube.var_name == "precip_anom": - cube.rename("Precipitation") - cube.var_name = "precip_patt" - if cube.var_name == "wind_anom": - cube.rename("Wind Speed") - cube.var_name = "wind_patt" - if cube.var_name == "pstar_anom": - cube.rename("Surface Pressure") - cube.var_name = "pstar_patt" - if cube.var_name == "swdown_anom": - cube.rename("Surface Downwelling Shortwave Radiation") - cube.var_name = "swdown_patt" - if cube.var_name == "lwdown_anom": - cube.rename("Surface Downwelling Longwave Radiation") - cube.var_name = "lwdown_patt" - - return cube - - -def rename_variables_base(cube): - """Rename variables and a coord for imogen_mode='off'. - - Parameters - ---------- - cube : cube - input cube - - Returns - ------- - cube : cube - cube with renamed variables - """ - if cube.var_name == "tl1_patt": - cube.rename("Air Temperature") - cube.var_name = "tas" - if cube.var_name == "range_tl1_patt": - cube.rename("Diurnal Range") - cube.var_name = "tas_range" - if cube.var_name == "rh1p5m_patt": - cube.rename("Relative Humidity") - cube.var_name = "hurs" - if cube.var_name == "ql1_patt": - cube.rename("Specific Humidity") - cube.var_name = "huss" - if cube.var_name == "precip_patt": - cube.rename("Precipitation") - cube.var_name = "pr" - if cube.var_name == "wind_patt": - cube.rename("Wind Speed") - cube.var_name = "sfcWind" - if cube.var_name == "pstar_patt": - cube.rename("Surface Pressure") - cube.var_name = "ps" - if cube.var_name == "swdown_patt": - cube.rename("Surface Downwelling Shortwave Radiation") - cube.var_name = "rsds" - if cube.var_name == "lwdown_patt": - cube.rename("Surface Downwelling Longwave Radiation") - cube.var_name = "rlds" - - cube.coord("imogen_drive").rename("month_number") - - return cube diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 2447160efd..97bc5f338a 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -208,3 +208,54 @@ def easy_parallise(func, sequence, cfg): return result return partial(easy_parallise, function) + + +def rename_variables(cube, orig_vars=True, new_extension=""): + """Rename variables and a coord to fit in JULES framework. + + Parameters + ---------- + cube : cube + input cube + orig_vars : bool + if True, rename to new var names with correct extension + new_extension : str + extension to add to variable names + + Returns + ------- + cube : cube + cube with renamed variables + """ + original_var_names = ["tas", "range_tl1", "huss", "pr", + "sfcWind", "ps", "rsds", "rlds"] + new_var_names = ["tl1", "range_tl1", "ql1", "precip", + "wind", "pstar", "swdown", "lwdown"] + long_var_names = [ + "Air Temperature", + "Diurnal Range", + "Specific Humidity", + "Precipitation", + "Wind Speed", + "Surface Pressure", + "Surface Downwelling Shortwave Radiation", + "Surface Downwelling Longwave Radiation" + ] + for orig_var, new_var, long_var in zip( + original_var_names, new_var_names, long_var_names + ): + if orig_vars: + if cube.var_name == f"{orig_var}": + cube.rename(long_var) + cube.var_name = f"{new_var}{new_extension}" + cube.coord("month_number").rename("imogen_drive") + else: + if cube.var_name == f"{new_var}_anom": + cube.rename(long_var) + cube.var_name = f"{new_var}_patt" + elif cube.var_name == f"{new_var}_patt": + cube.rename(long_var) + cube.var_name = f"{orig_var}" + cube.coord("imogen_drive").rename("month_number") + + return cube From 0b3d87783e7b1f1db045df6db25d22881c918af0 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 17:43:50 +0100 Subject: [PATCH 60/70] Quick fix --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 97bc5f338a..9eb5cb7794 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -210,7 +210,7 @@ def easy_parallise(func, sequence, cfg): return partial(easy_parallise, function) -def rename_variables(cube, orig_vars=True, new_extension=""): +def rename_variables(cube, has_orig_vars=True, new_extension=""): """Rename variables and a coord to fit in JULES framework. Parameters @@ -244,7 +244,7 @@ def rename_variables(cube, orig_vars=True, new_extension=""): for orig_var, new_var, long_var in zip( original_var_names, new_var_names, long_var_names ): - if orig_vars: + if has_orig_vars: if cube.var_name == f"{orig_var}": cube.rename(long_var) cube.var_name = f"{new_var}{new_extension}" From f5bb5b6b82916dd54850ee87bf054da7df13acc2 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Fri, 24 May 2024 18:06:51 +0100 Subject: [PATCH 61/70] Update docstring --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 9eb5cb7794..8eb112b325 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -217,7 +217,7 @@ def rename_variables(cube, has_orig_vars=True, new_extension=""): ---------- cube : cube input cube - orig_vars : bool + has_orig_vars : bool if True, rename to new var names with correct extension new_extension : str extension to add to variable names From 16b03e9b181cdde0db0046432aab369dd229501c Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Thu, 30 May 2024 14:49:16 +0100 Subject: [PATCH 62/70] Codacy fix --- .../climate_patterns/sub_functions.py | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 8eb112b325..e938fff991 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -179,37 +179,6 @@ def make_model_dirs(cfg, model): return model_work_dir, model_plot_dir -def parallelise(function, processes=None): - """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, sequence, cfg): - 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) - - def rename_variables(cube, has_orig_vars=True, new_extension=""): """Rename variables and a coord to fit in JULES framework. @@ -259,3 +228,34 @@ def rename_variables(cube, has_orig_vars=True, new_extension=""): cube.coord("imogen_drive").rename("month_number") return cube + + +def parallelise(function, processes=None): + """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, sequence, cfg): + 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) From 3b1ab494cdb8cc05eba8f00a493e0273aee4b7b8 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Mon, 3 Jun 2024 14:11:08 +0100 Subject: [PATCH 63/70] Fixed memory bug --- .../diag_scripts/climate_patterns/climate_patterns.py | 7 +++---- .../diag_scripts/climate_patterns/sub_functions.py | 10 +++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 31267d6319..9269f97931 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -392,7 +392,7 @@ def calculate_regressions( # create, and append cube of regression values month_list.append( - create_cube(tas, cube, regr_array, i, units=units) + create_cube(tas, cube.copy(), regr_array, i, units=units) ) month_list = month_list.merge_cube() @@ -581,10 +581,10 @@ def patterns(model, cfg): clim_list_final, anom_list_final = calculate_anomaly(clim_list, ts_list) for i, cube in enumerate(clim_list_final): - sf.rename_variables( + clim_list_final[i] = sf.rename_variables( cube, has_orig_vars=True, new_extension="_clim" ) - sf.rename_variables( + anom_list_final[i] = sf.rename_variables( anom_list_final[i], has_orig_vars=True, new_extension="_anom" ) @@ -630,7 +630,6 @@ def main(cfg): None """ input_data = cfg["input_data"].values() - print(input_data) parallelise = cfg["parallelise"] models = [] diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index e938fff991..f3cb1aac67 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -214,20 +214,20 @@ def rename_variables(cube, has_orig_vars=True, new_extension=""): original_var_names, new_var_names, long_var_names ): if has_orig_vars: - if cube.var_name == f"{orig_var}": - cube.rename(long_var) + if cube.var_name == orig_var: cube.var_name = f"{new_var}{new_extension}" cube.coord("month_number").rename("imogen_drive") + return cube else: if cube.var_name == f"{new_var}_anom": cube.rename(long_var) cube.var_name = f"{new_var}_patt" + return cube elif cube.var_name == f"{new_var}_patt": cube.rename(long_var) - cube.var_name = f"{orig_var}" + cube.var_name = orig_var cube.coord("imogen_drive").rename("month_number") - - return cube + return cube def parallelise(function, processes=None): From 26f337a1436d7d22c46e2105d0da99fec25faa58 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Mon, 3 Jun 2024 14:15:24 +0100 Subject: [PATCH 64/70] Codacy fix --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index f3cb1aac67..19a5aaa758 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -223,7 +223,7 @@ def rename_variables(cube, has_orig_vars=True, new_extension=""): cube.rename(long_var) cube.var_name = f"{new_var}_patt" return cube - elif cube.var_name == f"{new_var}_patt": + if cube.var_name == f"{new_var}_patt": cube.rename(long_var) cube.var_name = orig_var cube.coord("imogen_drive").rename("month_number") From 8df4001037974ada6f5a001f49c94538599da07f Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Mon, 3 Jun 2024 14:22:15 +0100 Subject: [PATCH 65/70] Codacy fix --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 19a5aaa758..2df6a0bf14 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -229,6 +229,8 @@ def rename_variables(cube, has_orig_vars=True, new_extension=""): cube.coord("imogen_drive").rename("month_number") return cube + return None + def parallelise(function, processes=None): """Parallelise any function, by George Ford, Met Office. From 78f409bbec6f0a8907b9cd271ccb2d0ce82cc5f3 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Mon, 3 Jun 2024 15:04:42 +0100 Subject: [PATCH 66/70] Improved plotting.py --- .../climate_patterns/climate_patterns.py | 20 ++-- .../diag_scripts/climate_patterns/plotting.py | 93 +++---------------- 2 files changed, 26 insertions(+), 87 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index 9269f97931..d2fd852a12 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -39,9 +39,7 @@ import sub_functions as sf from plotting import ( - plot_patterns_timeseries, - plot_anomalies_timeseries, - plot_climatologies_timeseries, + plot_timeseries, plot_patterns ) from esmvalcore.preprocessor import ( @@ -468,9 +466,19 @@ def save_outputs( # saving data + plotting if cfg["jules_mode"] is True: - plot_climatologies_timeseries(list_of_cubelists[0], plot_path) - plot_anomalies_timeseries(list_of_cubelists[1], plot_path) - plot_patterns_timeseries(list_of_cubelists[2], plot_path) + plot_timeseries( + list_of_cubelists[0], + plot_path, + "40 Year Climatologies, 1850-1889", + "Climatologies" + ) + plot_timeseries( + list_of_cubelists[1], + plot_path, + "Anomaly Timeseries, 1850-2100", + "Anomalies" + ) + plot_patterns(list_of_cubelists[2], plot_path) cube_saver( list_of_cubelists, work_path, diff --git a/esmvaltool/diag_scripts/climate_patterns/plotting.py b/esmvaltool/diag_scripts/climate_patterns/plotting.py index 8711e2864d..447055a392 100644 --- a/esmvaltool/diag_scripts/climate_patterns/plotting.py +++ b/esmvaltool/diag_scripts/climate_patterns/plotting.py @@ -86,51 +86,7 @@ def plot_patterns(cube_list, plot_path): fig.savefig(os.path.join(plot_path, "Patterns Timeseries"), dpi=300) -def plot_patterns_timeseries(cubelist, plot_path): - """Plot timeseries and maps of climatologies, anomalies and patterns. - - Parameters - ---------- - cubelist: cubelist - input cubelist for plotting per variable - plot_path : path - path to plot_dir - - Returns - ------- - None - """ - fig, axs = plt.subplots(3, 3, figsize=(14, 12), sharex=True) - fig.suptitle("Patterns from a random grid-cell", fontsize=18, y=0.98) - - plt.figure(figsize=(14, 12)) - plt.subplots_adjust(hspace=0.5) - plt.suptitle("Global Patterns, January", fontsize=18, y=0.95) - - for j, cube in enumerate(cubelist): - # determining plot positions - x_pos, y_pos = subplot_positions(j) - - months = np.arange(1, 13) - axs[x_pos, y_pos].plot(months, cube[:, 50, 50].data) - axs[x_pos, y_pos].set_ylabel( - str(cube.var_name) + " / " + str(cube.units)) - if j > 5: - axs[x_pos, y_pos].set_xlabel("Time") - - # January patterns - plt.subplot(3, 3, j + 1) - qplt.pcolormesh(cube[0]) - - fig.tight_layout() - fig.savefig(os.path.join(plot_path, "Patterns Timeseries"), dpi=300) - - plt.tight_layout() - plt.savefig(os.path.join(plot_path, "Patterns"), dpi=300) - plt.close() - - -def plot_anomalies_timeseries(cubelist, plot_path): +def plot_timeseries(cubelist, plot_path, title, save_name): """Plot timeseries and maps of climatologies, anomalies and patterns. Parameters @@ -139,59 +95,34 @@ def plot_anomalies_timeseries(cubelist, plot_path): input cubelist for plotting per variable plot_path : path path to plot_dir + title: str + title for the figure + save_name: str + name for the saved figure Returns ------- None """ fig, axs = plt.subplots(3, 3, figsize=(14, 12), sharex=True) - fig.suptitle("Anomaly Timeseries, 1850-2100", fontsize=18, y=0.98) + fig.suptitle(f"{title}", fontsize=18, y=0.98) for j, cube in enumerate(cubelist): # determining plot positions x_pos, y_pos = subplot_positions(j) yrs = (1850 + np.arange(cube.shape[0])).astype("float") + months = np.arange(1, 13) # anomaly timeseries avg_cube = area_statistics(cube, 'mean').data - axs[x_pos, y_pos].plot(yrs, avg_cube) - axs[x_pos, - y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) - if j > 5: - axs[x_pos, y_pos].set_xlabel("Time") - - fig.tight_layout() - fig.savefig(os.path.join(plot_path, "Anomalies"), dpi=300) - - -def plot_climatologies_timeseries(cubelist, plot_path): - """Plot timeseries and maps of climatologies, anomalies and patterns. - - Parameters - ---------- - cubelist : cubelist - input cubelist for plotting per variable - plot_path : path - path to plot_dir - - Returns - ------- - None - """ - fig, axs = plt.subplots(3, 3, figsize=(14, 12), sharex=True) - fig.suptitle("40 Year Climatologies, 1850-1889", fontsize=18, y=0.98) - - for j, cube in enumerate(cubelist): - # determining plot positions - x_pos, y_pos = subplot_positions(j) - yrs = (1850 + np.arange(cube.shape[0])).astype("float") - - avg_cube = area_statistics(cube, 'mean').data - axs[x_pos, y_pos].plot(yrs, avg_cube) + if save_name == "Climatologies": + axs[x_pos, y_pos].plot(months, avg_cube) + else: + axs[x_pos, y_pos].plot(yrs, avg_cube) axs[x_pos, y_pos].set_ylabel(cube.long_name + " / " + str(cube.units)) if j > 5: axs[x_pos, y_pos].set_xlabel("Time") fig.tight_layout() - fig.savefig(os.path.join(plot_path, "Climatologies"), dpi=300) + fig.savefig(os.path.join(plot_path, f"{save_name}"), dpi=300) From 3a8dda5ecfe843ffb35d00c57db861d963d8ef1d Mon Sep 17 00:00:00 2001 From: Greg Munday <100290135+mo-gregmunday@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:26:04 +0100 Subject: [PATCH 67/70] Update esmvaltool/diag_scripts/climate_patterns/sub_functions.py Co-authored-by: Emma Hogan --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index 2df6a0bf14..c6a152fb35 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -69,7 +69,7 @@ def ocean_fraction_calc(sftlf): return ocean_frac, land_frac -def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=None): +def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=False): """Calculate the global mean of a variable in a cube. Parameters From 8eb09ddacdb65ef6e2f42362d2753e04bbcac1f3 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Wed, 19 Jun 2024 12:34:07 +0100 Subject: [PATCH 68/70] Docstring corrections and recipe correction --- .../diag_scripts/climate_patterns/climate_patterns.py | 8 ++++---- esmvaltool/recipes/recipe_climate_patterns.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py index d2fd852a12..7fdb98a293 100644 --- a/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py +++ b/esmvaltool/diag_scripts/climate_patterns/climate_patterns.py @@ -237,12 +237,12 @@ def regression(tas, cube_data, area, ocean_frac=None, land_frac=None): near-surface air temperature cube_data : arr cube.data array of a variable + area: str + area over which to calculate patterns ocean_frac: cube gridded ocean fraction land_frac: cube gridded land fraction - area: str - area over which to calculate patterns Returns ------- @@ -338,12 +338,12 @@ def calculate_regressions( ---------- anom_list : cubelist cube list of variables as anomalies + area: str + area over which to calculate patterns ocean_frac: cube gridded ocean fraction land_frac: cube gridded land fraction - area: str - area over which to calculate patterns yrs : int int to specify length of scenario diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index dde7515491..b43edc804d 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -244,6 +244,6 @@ diagnostics: scripts: climate_patterns_script: script: climate_patterns/climate_patterns.py - jules_mode: true # options: true, false + jules_mode: false # options: true, false parallelise: true # options: true, false area: global # options global, land. If land, uncomment landfrac recipe settings From dfb6e703e69a16c943278433c0ba81ee871da3f1 Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Wed, 19 Jun 2024 12:39:21 +0100 Subject: [PATCH 69/70] Line too long fix --- esmvaltool/diag_scripts/climate_patterns/sub_functions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py index c6a152fb35..4b3fe00141 100644 --- a/esmvaltool/diag_scripts/climate_patterns/sub_functions.py +++ b/esmvaltool/diag_scripts/climate_patterns/sub_functions.py @@ -69,7 +69,11 @@ def ocean_fraction_calc(sftlf): return ocean_frac, land_frac -def area_avg_landsea(cube, ocean_frac, land_frac, land=True, return_cube=False): +def area_avg_landsea(cube, + ocean_frac, + land_frac, + land=True, + return_cube=False): """Calculate the global mean of a variable in a cube. Parameters From f4bab69bbf9179f64e2f5f1af26078c8828b3cee Mon Sep 17 00:00:00 2001 From: mo-gregmunday Date: Wed, 19 Jun 2024 15:32:33 +0100 Subject: [PATCH 70/70] Updated recipe default --- esmvaltool/recipes/recipe_climate_patterns.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvaltool/recipes/recipe_climate_patterns.yml b/esmvaltool/recipes/recipe_climate_patterns.yml index b43edc804d..08e0c51779 100644 --- a/esmvaltool/recipes/recipe_climate_patterns.yml +++ b/esmvaltool/recipes/recipe_climate_patterns.yml @@ -245,5 +245,5 @@ diagnostics: climate_patterns_script: script: climate_patterns/climate_patterns.py jules_mode: false # options: true, false - parallelise: true # options: true, false + parallelise: false # options: true, false area: global # options global, land. If land, uncomment landfrac recipe settings