Skip to content

climate_ref_esmvaltool.diagnostics.tcr #

TransientClimateResponse #

Bases: ESMValToolDiagnostic

Calculate the global mean transient climate response for a dataset.

Source code in packages/climate-ref-esmvaltool/src/climate_ref_esmvaltool/diagnostics/tcr.py
class TransientClimateResponse(ESMValToolDiagnostic):
    """
    Calculate the global mean transient climate response for a dataset.
    """

    name = "Transient Climate Response"
    slug = "transient-climate-response"
    version = 2
    base_recipe = "ref/recipe_ref_tcr.yml"

    data_requirements = (
        (
            DataRequirement(
                source_type=SourceDatasetType.CMIP6,
                filters=(
                    FacetFilter(
                        facets={
                            "variable_id": ("tas",),
                            "experiment_id": "1pctCO2",
                            "table_id": "Amon",
                        },
                    ),
                ),
                group_by=("source_id", "member_id", "grid_label"),
                constraints=(
                    AddParentDataset.from_defaults(SourceDatasetType.CMIP6),
                    RequireContiguousTimerange(group_by=("instance_id",)),
                    AddSupplementaryDataset.from_defaults("areacella", SourceDatasetType.CMIP6),
                ),
            ),
        ),
        (
            DataRequirement(
                source_type=SourceDatasetType.CMIP7,
                filters=(
                    FacetFilter(
                        facets={
                            "branded_variable": "tas_tavg-h2m-hxy-u",
                            "experiment_id": "1pctCO2",
                            "frequency": "mon",
                            "region": "glb",
                        },
                    ),
                ),
                group_by=("source_id", "variant_label", "grid_label"),
                constraints=(
                    AddParentDataset.from_defaults(SourceDatasetType.CMIP7),
                    RequireContiguousTimerange(group_by=("instance_id",)),
                    AddSupplementaryDataset.from_defaults("areacella", SourceDatasetType.CMIP7),
                ),
            ),
        ),
    )
    facets = ("grid_label", "member_id", "variant_label", "source_id", "region", "metric")
    series = (
        SeriesDefinition(
            file_pattern="tcr/calculate/{source_id}*.nc",
            dimensions={
                "statistic": "global annual mean tas anomaly relative to linear fit of piControl run",
            },
            values_name="tas_anomaly",
            index_name="time",
            attributes=[],
        ),
    )
    files = (
        FileDefinition(
            file_pattern="plots/tcr/calculate/*.png",
            dimensions={
                "statistic": "global annual mean tas anomaly relative to linear fit of piControl run",
            },
        ),
        FileDefinition(
            file_pattern="work/tcr/calculate/tcr.nc",
            dimensions={"metric": "tcr"},
        ),
    )

    test_data_spec = TestDataSpecification(
        test_cases=(
            TestCase(
                name="cmip6",
                description="Test with CMIP6 data.",
                requests=(
                    CMIP6Request(
                        slug="cmip6",
                        facets={
                            "experiment_id": ["1pctCO2", "piControl"],
                            "source_id": "CanESM5",
                            "variable_id": ["areacella", "tas"],
                            "frequency": ["fx", "mon"],
                        },
                        remove_ensembles=True,
                    ),
                ),
            ),
            TestCase(
                name="cmip7",
                description="Test with CMIP7 data.",
                requests=(
                    CMIP7Request(
                        slug="cmip7",
                        facets={
                            "experiment_id": ["1pctCO2", "piControl"],
                            "source_id": "CanESM5",
                            "variable_id": ["areacella", "tas"],
                            "branded_variable": [
                                "areacella_ti-u-hxy-u",
                                "tas_tavg-h2m-hxy-u",
                            ],
                            "variant_label": "r1i1p1f1",
                            "frequency": ["fx", "mon"],
                            "region": "glb",
                        },
                        remove_ensembles=True,
                    ),
                ),
            ),
        )
    )

    @staticmethod
    def update_recipe(
        recipe: Recipe,
        input_files: dict[SourceDatasetType, pandas.DataFrame],
    ) -> None:
        """Update the recipe."""
        # Prepare updated datasets section in recipe. It contains two datasets,
        # one for the "1pctCO2" and one for the "piControl" experiment.
        cmip_source = get_cmip_source_type(input_files)
        df = input_files[cmip_source]
        recipe["datasets"] = get_child_and_parent_dataset(
            df[df.variable_id == "tas"],
            parent_experiment="piControl",
            child_duration_in_years=140,
            parent_offset_in_years=0,
            parent_duration_in_years=140,
        )

        # Delete branding suffixes from dataset entries because they are
        # variable-specific
        for dataset in recipe["datasets"]:
            dataset.pop("branding_suffix", None)

        # For CMIP6, delete all appearances of branding suffixes
        if cmip_source == SourceDatasetType.CMIP6:
            for variable in recipe["diagnostics"]["tcr"]["variables"].values():
                variable.pop("branding_suffix", None)

    @staticmethod
    def format_result(
        result_dir: Path,
        execution_dataset: ExecutionDatasetCollection,
        metric_args: MetricBundleArgs,
        output_args: OutputBundleArgs,
    ) -> tuple[CMECMetric, CMECOutput]:
        """Format the result."""
        tcr_ds = xarray.open_dataset(result_dir / "work" / "tcr" / "calculate" / "tcr.nc")
        tcr = float(fillvalues_to_nan(tcr_ds["tcr"].values)[0])

        # Update the diagnostic bundle arguments with the computed diagnostics.
        metric_args[MetricCV.DIMENSIONS.value] = {
            "json_structure": [
                "region",
                "metric",
            ],
            "region": {"global": {}},
            "metric": {"tcr": {}},
        }
        metric_args[MetricCV.RESULTS.value] = {
            "global": {
                "tcr": tcr,
            },
        }

        return CMECMetric.model_validate(metric_args), CMECOutput.model_validate(output_args)

format_result(result_dir, execution_dataset, metric_args, output_args) staticmethod #

Format the result.

Source code in packages/climate-ref-esmvaltool/src/climate_ref_esmvaltool/diagnostics/tcr.py
@staticmethod
def format_result(
    result_dir: Path,
    execution_dataset: ExecutionDatasetCollection,
    metric_args: MetricBundleArgs,
    output_args: OutputBundleArgs,
) -> tuple[CMECMetric, CMECOutput]:
    """Format the result."""
    tcr_ds = xarray.open_dataset(result_dir / "work" / "tcr" / "calculate" / "tcr.nc")
    tcr = float(fillvalues_to_nan(tcr_ds["tcr"].values)[0])

    # Update the diagnostic bundle arguments with the computed diagnostics.
    metric_args[MetricCV.DIMENSIONS.value] = {
        "json_structure": [
            "region",
            "metric",
        ],
        "region": {"global": {}},
        "metric": {"tcr": {}},
    }
    metric_args[MetricCV.RESULTS.value] = {
        "global": {
            "tcr": tcr,
        },
    }

    return CMECMetric.model_validate(metric_args), CMECOutput.model_validate(output_args)

update_recipe(recipe, input_files) staticmethod #

Update the recipe.

Source code in packages/climate-ref-esmvaltool/src/climate_ref_esmvaltool/diagnostics/tcr.py
@staticmethod
def update_recipe(
    recipe: Recipe,
    input_files: dict[SourceDatasetType, pandas.DataFrame],
) -> None:
    """Update the recipe."""
    # Prepare updated datasets section in recipe. It contains two datasets,
    # one for the "1pctCO2" and one for the "piControl" experiment.
    cmip_source = get_cmip_source_type(input_files)
    df = input_files[cmip_source]
    recipe["datasets"] = get_child_and_parent_dataset(
        df[df.variable_id == "tas"],
        parent_experiment="piControl",
        child_duration_in_years=140,
        parent_offset_in_years=0,
        parent_duration_in_years=140,
    )

    # Delete branding suffixes from dataset entries because they are
    # variable-specific
    for dataset in recipe["datasets"]:
        dataset.pop("branding_suffix", None)

    # For CMIP6, delete all appearances of branding suffixes
    if cmip_source == SourceDatasetType.CMIP6:
        for variable in recipe["diagnostics"]["tcr"]["variables"].values():
            variable.pop("branding_suffix", None)