diff --git a/CMEW/app/configure_for/bin/test_update_recipe_file.py b/CMEW/app/configure_for/bin/test_update_recipe_file.py index e26dd3fe..df0a3d7f 100644 --- a/CMEW/app/configure_for/bin/test_update_recipe_file.py +++ b/CMEW/app/configure_for/bin/test_update_recipe_file.py @@ -9,9 +9,18 @@ @pytest.fixture def mock_env_vars(monkeypatch): + # Time window monkeypatch.setenv("START_YEAR", "1993") monkeypatch.setenv("NUMBER_OF_YEARS", "1") + # Reference run metadata + monkeypatch.setenv("REF_MODEL_ID", "HadGEM3-GC31-LL") + monkeypatch.setenv("REF_VARIANT_LABEL", "r1i1p1f3") + + # Evaluation run metadata + monkeypatch.setenv("MODEL_ID", "UKESM1-0-LL") + monkeypatch.setenv("VARIANT_LABEL", "r1i1p1f1") + @pytest.fixture def path_to_updated_recipe_kgo(): @@ -38,6 +47,12 @@ def path_to_mock_original_recipe(): def test_update_recipe( mock_env_vars, path_to_updated_recipe_kgo, path_to_mock_original_recipe ): + """update_recipe should produce the KGO with both datasets updated. + + - Dataset[0] uses REF_MODEL_ID / REF_VARIANT_LABEL + - Dataset[1] uses MODEL_ID / VARIANT_LABEL + - start_year and end_year are set from START_YEAR / NUMBER_OF_YEARS + """ with open(path_to_updated_recipe_kgo, "r") as file_handle: expected = yaml.safe_load(file_handle) actual = update_recipe(path_to_mock_original_recipe) @@ -51,6 +66,7 @@ def test_main( path_to_mock_original_recipe, tmp_path, ): + """main() should overwrite the recipe in-place with the updated content.""" # Copy the original recipe to a tmp_path location to allow it to be # overwritten. path_to_temp_recipe = tmp_path / "tmp_recipe.yml" @@ -58,12 +74,12 @@ def test_main( # Mock the environmental variable 'RECIPE PATH' to the tmp_path location # where the original recipe is stored. - monkeypatch.setenv("RECIPE_PATH", path_to_temp_recipe) + monkeypatch.setenv("RECIPE_PATH", str(path_to_temp_recipe)) main() with open(path_to_temp_recipe, "r") as file_handle_1: - actual = file_handle_1.readlines() + actual_lines = file_handle_1.readlines() with open(path_to_updated_recipe_kgo, "r") as file_handle_2: kgo_with_comment = file_handle_2.readlines() @@ -72,4 +88,4 @@ def test_main( # 'test_updated_radiation_budget_recipe.yml'. kgo_without_comment = kgo_with_comment[5:] - assert actual == kgo_without_comment + assert actual_lines == kgo_without_comment diff --git a/CMEW/app/configure_for/bin/update_recipe_file.py b/CMEW/app/configure_for/bin/update_recipe_file.py index a73b2ad4..87398cc4 100755 --- a/CMEW/app/configure_for/bin/update_recipe_file.py +++ b/CMEW/app/configure_for/bin/update_recipe_file.py @@ -15,8 +15,10 @@ def update_recipe(recipe_path): """Update the ESMValTool recipe. * Read the ESMValTool recipe YAML file from the provided ``recipe_path`` - * Update the dataset section of the recipe with a) CMEW required key/values - and b) user configurables values from the Rose suite configuration. + * Update the datasets section of the recipe with: + - CMEW required key/values + - User configurable values from the Rose suite configuration + for both the reference and evaluation model runs. Recipe file/datasets section snippet (human written YAML):: @@ -31,16 +33,17 @@ def update_recipe(recipe_path): Updated recipe file/datasets section snippet (machine written YAML):: datasets: - - {dataset: , end_year: , ensemble: , - end_year: , exp: , grid: , project: , - start_year: } - - {activity: , dataset: , end_year: , - ensemble: , exp: , grid: , project: , + - {dataset: , end_year: , ensemble: , + exp: , grid: , project: , start_year: } + - {activity: , dataset: , end_year: , + ensemble: , exp: , grid: , project: , start_year: } Notes ----- - The updated recipe includes one additional CMEW required key: "Activity". + The updated recipe includes: + * Reference dataset (index 0) using REF_MODEL_ID and REF_VARIANT_LABEL + * Evaluation dataset (index 1) using MODEL_ID and VARIANT_LABEL Parameters ---------- @@ -49,28 +52,60 @@ def update_recipe(recipe_path): Returns ------- - recipe: dict[str, union[str, int]] + recipe: dict The content of the ESMValTool recipe with updated datasets section. """ + # Time window from environment start_year = int(os.environ["START_YEAR"]) end_year = ( int(os.environ["START_YEAR"]) + int(os.environ["NUMBER_OF_YEARS"]) - 1 ) + + # Model metadata from environment + ref_model_id = os.environ["REF_MODEL_ID"] + ref_variant = os.environ["REF_VARIANT_LABEL"] + eval_model_id = os.environ["MODEL_ID"] + eval_variant = os.environ["VARIANT_LABEL"] + with open(recipe_path, "r") as file_handle: recipe = yaml.safe_load(file_handle) - first_dataset = recipe["datasets"][0] - second_dataset = recipe["datasets"][1] - first_dataset.update({"start_year": start_year, "end_year": end_year}) - second_dataset.update( + + datasets = recipe.get("datasets", []) + if len(datasets) < 2: + raise ValueError( + "Expected at least two datasets in the recipe, " + "one for the reference and one for the evaluation run." + ) + + # Reference dataset: treat as a GCModelDev / ESMVal / amip run, + # using REF_MODEL_ID & REF_VARIANT_LABEL, with the configured time window. + ref_dataset = datasets[0] + ref_dataset.update( + { + "dataset": ref_model_id, + "project": "ESMVal", + "exp": "amip", + "activity": "ESMVal", + "ensemble": ref_variant, + "start_year": start_year, + "end_year": end_year, + } + ) + + # Evaluation dataset: ESMVal / amip run using MODEL_ID and VARIANT_LABEL + eval_dataset = datasets[1] + eval_dataset.update( { + "dataset": eval_model_id, "project": "ESMVal", "exp": "amip", "activity": "ESMVal", - "ensemble": "r1i1p1f1", + "ensemble": eval_variant, "start_year": start_year, "end_year": end_year, } ) + return recipe diff --git a/CMEW/app/configure_standardise/bin/test_create_request_file.py b/CMEW/app/configure_standardise/bin/test_create_request_file.py index 5ac148c0..0f57a306 100644 --- a/CMEW/app/configure_standardise/bin/test_create_request_file.py +++ b/CMEW/app/configure_standardise/bin/test_create_request_file.py @@ -16,6 +16,7 @@ def test_create_request(monkeypatch): monkeypatch.setenv("ROOT_DATA_DIR", "/path/to/data/dir/") monkeypatch.setenv("SUITE_ID", "u-az513") monkeypatch.setenv("VARIABLES_PATH", "/path/to/variables.txt") + monkeypatch.setenv("VARIANT_LABEL", "r1i1p1f1") config = create_request() actual = { diff --git a/CMEW/app/unittest/kgo/test_updated_radiation_budget_recipe.yml b/CMEW/app/unittest/kgo/test_updated_radiation_budget_recipe.yml index a59c1722..c13d7dff 100644 --- a/CMEW/app/unittest/kgo/test_updated_radiation_budget_recipe.yml +++ b/CMEW/app/unittest/kgo/test_updated_radiation_budget_recipe.yml @@ -4,12 +4,13 @@ # To be used for testing purposes. Represents KGO for `update_recipe`. --- datasets: -- dataset: HadGEM3-GC31-LL +- activity: ESMVal + dataset: HadGEM3-GC31-LL end_year: 1993 ensemble: r1i1p1f3 - exp: historical + exp: amip grid: gn - project: CMIP6 + project: ESMVal start_year: 1993 - activity: ESMVal dataset: UKESM1-0-LL diff --git a/doc/source/user_guide/workflow.rst b/doc/source/user_guide/workflow.rst index a01d1163..04646b3d 100644 --- a/doc/source/user_guide/workflow.rst +++ b/doc/source/user_guide/workflow.rst @@ -12,38 +12,39 @@ An overview of the workflow ``install_env_file`` :Description: - Activates the environment for |ESMValTool|, based on the ``SITE`` provided + Activates the environment for |ESMValTool|, based on the ``SITE`` provided. :Runs on: Localhost :Executes: - The ``install_env_file.sh`` script from the |Rose| app + The ``install_env_file.sh`` script from the |Rose| app. :Details: - Runs once at the start of the workflow + Runs once at the start of the workflow. ``configure_recipe`` :Description: Creates and modifies the |ESMValTool| user configuration file, - and writes it to the cylc workflow ``share/etc`` directory + and writes it to the cylc workflow ``share/etc`` directory. :Runs on: Localhost :Executes: - The ``configure_recipe.py`` script from the |Rose| app + The ``configure_recipe.py`` script from the |Rose| app. :Details: Runs immediately after the successful completion of the ``install_env_file`` job. Temporarily, the modified ESMValTool developer configuration file is copied from - the ``configure_recipe`` app to the ``share/etc`` directory in the installed workflow + the ``configure_recipe`` app to the ``share/etc`` directory in the installed workflow. ``configure_for`` :Description: Copies an updated version of the |ESMValTool| recipe - into the cylc workflow ``share/etc`` directory - in the installed workflow + into the Cylc workflow ``share/etc`` directory + in the installed workflow and configures it + to use standardised model data. :Runs on: Localhost :Executes: For the required recipe, executes the ``esmvaltool recipes get`` command - followed by the ``update_recipe_file.py`` script from the |Rose| app + followed by the ``update_recipe_file.py`` script from the |Rose| app. :Details: Runs once for each recipe, immediately after the successful completion @@ -51,35 +52,44 @@ An overview of the workflow The recipe is updated with CMEW required variables (e.g. "Activity": "ESMVal") and also with user configurable variables - from the |Rose Edit GUI|_/``rose-suite.conf`` + from the |Rose Edit GUI|_/``rose-suite.conf``, + for both model runs. :Families: ``RECIPE`` ``configure_standardise`` :Description: - Creates the ``request.json`` file and variables list which are needed to run - |CDDS| and creates the |CDDS| directory structure. + Creates the |CDDS| request metadata + and variables list required to standardise two model development runs, + then prepares the |CDDS| directory structure. :Runs on: Localhost :Executes: - The ``configure_standardise.sh`` script from the |Rose| app + The ``configure_standardise.sh`` script from the |Rose| app. :Details: Runs once for each recipe, immediately after the successful - completion of the ``configure_for`` job + completion of the ``configure_for`` job. + Generates |CDDS| request metadata for each model run (reference and evaluation): + ``request_ref.json``, ``request_eval.json``. + Reads model-specific values from the workflow environment. + Creates the required directory structure to support + multiple |CDDS| standardisation workflows + within the same |CMEW| cycle. ``standardise_model_data`` :Description: - Launches the |CDDS| workflow and converts the data into a |CMIP| compliant - format for |ESMValTool| + Launches the CDDS workflow and converts both model runs into |CMIP|-compliant + datasets suitable for |ESMValTool| evaluation. :Runs on: Localhost :Executes: The ``cdds_convert`` command and the ``restructure_dirs.sh`` script - from the |Rose| app + from the |Rose| app. :Details: Runs after the successful completion of the ``configure_standardise`` job. - The ``restructure_dirs.sh`` script moves the standardised data into - a directory with a BADC DRS structure so that |ESMValTool| can find the data + Executes |CDDS| standardisation for both the reference and evaluation model run + to produces |CMIP|-compliant output for each. + Uses ``restructure_dirs.sh`` to move standardised data into a BADC DRS structure. ``housekeeping`` :Description: @@ -97,13 +107,13 @@ An overview of the workflow Runs the requested recipes using |ESMValTool| :Runs on: ``COMPUTE``, which depends on the ``SITE``; at the Met Office, the - ``run_recipe`` jobs will run on SPICE + ``run_recipe`` jobs will run on SPICE. :Executes: The |ESMValTool| command line script :Details: Runs once for each recipe, after the successful completion of the ``standardise_model_data`` - and the ``configure_recipe`` jobs + and the ``configure_recipe`` jobs. :Families: ``COMPUTE``, ``RECIPE`` @@ -127,7 +137,7 @@ An overview of the workflow ``pytest`` from the |Rose| app :Details: Runs on its own when ``-O unittest`` command is invoked, or runs alongside the - full workflow when running with ``-O test`` + full workflow when running with ``-O test``. Design considerations ---------------------