diff --git a/changelog/548.feature.md b/changelog/548.feature.md new file mode 100644 index 000000000..2801c7770 --- /dev/null +++ b/changelog/548.feature.md @@ -0,0 +1 @@ +Update `ilamb3`, remove the `ohc-noaa` diagnostic and add the `evspsbl-pr` diagnostic. diff --git a/packages/climate-ref-core/src/climate_ref_core/datasets.py b/packages/climate-ref-core/src/climate_ref_core/datasets.py index 4d6f377bc..25ba74f78 100644 --- a/packages/climate-ref-core/src/climate_ref_core/datasets.py +++ b/packages/climate-ref-core/src/climate_ref_core/datasets.py @@ -3,7 +3,7 @@ """ import hashlib -from collections.abc import Collection, Iterable, Iterator +from collections.abc import Collection, Iterable, Iterator, Mapping from typing import Any import pandas as pd @@ -15,7 +15,7 @@ __all__ = ["Selector", "SourceDatasetType"] -def _clean_facets(raw_values: dict[str, str | Collection[str]]) -> dict[str, tuple[str, ...]]: +def _clean_facets(raw_values: Mapping[str, str | Collection[str]]) -> dict[str, tuple[str, ...]]: """ Clean the value of a facet filter to a tuple of strings """ diff --git a/packages/climate-ref-ilamb/pyproject.toml b/packages/climate-ref-ilamb/pyproject.toml index 42cb97b81..e4af3cf38 100644 --- a/packages/climate-ref-ilamb/pyproject.toml +++ b/packages/climate-ref-ilamb/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ "climate-ref-core", - "ilamb3>=2025.9.9,<2025.12", # to be relaxed in #548 + "ilamb3>=2026.2.19", "scipy<1.16", # https://github.com/statsmodels/statsmodels/issues/9584 "xarray<2025.11", # ilamb3 incompatibility with integrate_space units handling ] diff --git a/packages/climate-ref-ilamb/src/climate_ref_ilamb/configure/ilamb.yaml b/packages/climate-ref-ilamb/src/climate_ref_ilamb/configure/ilamb.yaml index e0c993559..3ef3f4e4c 100644 --- a/packages/climate-ref-ilamb/src/climate_ref_ilamb/configure/ilamb.yaml +++ b/packages/climate-ref-ilamb/src/climate_ref_ilamb/configure/ilamb.yaml @@ -2,10 +2,9 @@ registry: ilamb gpp-WECANN: sources: - # TODO: Update to use the obs4REF equiv - gpp: ilamb/gpp/WECANN/gpp.nc + gpp: obs4REF/ColumbiaU/WECANN-1-0/mon/gpp/gn/20250516/gpp_mon_WECANN-1-0_REF_gn_200701-201512.nc relationships: - pr: ilamb/pr/GPCPv2.3/pr.nc + pr: obs4REF/NOAA-NCEI/GPCP-2-3/mon/pr/gn/v20210727/pr_mon_GPCP-2-3_PCMDI_gn_197901-201907.nc tas: ilamb/tas/CRU4.02/tas.nc variable_cmap: Greens @@ -16,8 +15,9 @@ gpp-FLUXNET2015: mrro-LORA: sources: - # TODO: Update to use the obs4REF equiv - mrro: ilamb/mrro/LORA/LORA.nc + mrro: + obs_source: obs4ref + source_id: LORA-1-1 variable_cmap: Blues mrsos-WangMao: @@ -36,17 +36,24 @@ cSoil-HWSD2: cSoil: ilamb/cSoil/HWSD2/cSoil_fx_HWSD2_19600101-20220101.nc variable_cmap: viridis -lai-AVH15C1: +lai-NOAA-NCEI-LAI-AVHRR-5-0: sources: - lai: ilamb/lai/AVH15C1/lai.nc + lai: + obs_source: obs4mips + source_id: NOAA-NCEI-LAI-AVHRR-5-0 variable_cmap: Greens nbp-Hoffman: analyses: - nbp sources: - # TODO: Update to use the obs4REF equiv - nbp: ilamb/nbp/HOFFMAN/nbp_1850-2010.nc + nbp: + obs_source: obs4ref + variable_id: nbp + source_id: Hoffman + transforms: + - integrate_space: + varname: nbp snc-ESACCI: sources: @@ -58,3 +65,17 @@ burntFractionAll-GFED: alternate_vars: - burntFractionAll test_source_id: CESM2 + +emp-GLEAMGPCP2.3: + analysis_variable: emp + sources: + et: ilamb/evspsbl/GLEAMv3.3a/et.nc + pr: obs4REF/NOAA-NCEI/GPCP-2-3/mon/pr/gn/v20210727/pr_mon_GPCP-2-3_PCMDI_gn_197901-201907.nc + transforms: + - select_time: + vmin: 2000-01 + vmax: 2015-01 + - expression: + expr: emp = et - pr + alternate_vars: + - evspsbl diff --git a/packages/climate-ref-ilamb/src/climate_ref_ilamb/configure/iomb.yaml b/packages/climate-ref-ilamb/src/climate_ref_ilamb/configure/iomb.yaml index 26140a939..781ea7e94 100644 --- a/packages/climate-ref-ilamb/src/climate_ref_ilamb/configure/iomb.yaml +++ b/packages/climate-ref-ilamb/src/climate_ref_ilamb/configure/iomb.yaml @@ -2,7 +2,7 @@ registry: iomb thetao-WOA2023-surface: sources: - # TODO: Update to use the obs4REF equiv + # NOTE: obs4REF equiv starts beyond historical, cannot use for CMIP6 thetao: ilamb/WOA/thetao_mon_WOA_A5B4_gn_200501-201412.nc variable_cmap: Reds transforms: @@ -13,7 +13,7 @@ thetao-WOA2023-surface: so-WOA2023-surface: sources: - # TODO: Update to use the obs4REF equiv + # NOTE: obs4REF equiv starts beyond historical, cannot use for CMIP6 so: ilamb/WOA/so_mon_WOA_A5B4_gn_200501-201412.nc transforms: - select_depth: @@ -30,20 +30,5 @@ amoc-RAPID: transforms: - msftmz_to_rapid sources: - # TODO: Update to use the obs4REF equiv + # obs4REF equiv does not work amoc: ilamb/RAPID/amoc_mon_RAPID_BE_NA_200404-202302.nc - -ohc-NOAA: - sources: - ohc: ilamb/NOAA/ohc_yr_OHC_BE_gm_200506-202406.nc - related_vars: - - thetao - - volcello - transforms: - - select_depth: - vmin: 0 - vmax: 2000 - - ocean_heat_content: - reference_year: 2005 - analyses: - - accumulate diff --git a/packages/climate-ref-ilamb/src/climate_ref_ilamb/dataset_registry/ilamb.txt b/packages/climate-ref-ilamb/src/climate_ref_ilamb/dataset_registry/ilamb.txt index ed6e401ac..3788f95ec 100644 --- a/packages/climate-ref-ilamb/src/climate_ref_ilamb/dataset_registry/ilamb.txt +++ b/packages/climate-ref-ilamb/src/climate_ref_ilamb/dataset_registry/ilamb.txt @@ -11,3 +11,4 @@ ilamb/tas/CRU4.02/tas.nc sha1:2674da18a1a93483b50b1626e7a7ab741bf53d09 ilamb/nbp/HOFFMAN/nbp_1850-2010.nc sha1:8350af00614d6afc6b70ad314aa499a9ece80ec2 ilamb/snc/Snow-cci/snc_mon_Snow-cci_BE_gn_198201-201906.nc sha1:c0bfecd2f8b886e9301428d28bb6ff0507601be2 ilamb/burntFractionAll/GFED/burntArea.nc sha1:cf9d73c6a8bfc594737c9ba6ca4df613df4a28ab +ilamb/evspsbl/GLEAMv3.3a/et.nc sha1:5aaf73949af8c6b509ef16f684aa8efeccd983e2 diff --git a/packages/climate-ref-ilamb/src/climate_ref_ilamb/dataset_registry/iomb.txt b/packages/climate-ref-ilamb/src/climate_ref_ilamb/dataset_registry/iomb.txt index e40f733b1..ef1a65ffe 100644 --- a/packages/climate-ref-ilamb/src/climate_ref_ilamb/dataset_registry/iomb.txt +++ b/packages/climate-ref-ilamb/src/climate_ref_ilamb/dataset_registry/iomb.txt @@ -1,4 +1,3 @@ ilamb/WOA/so_mon_WOA_A5B4_gn_200501-201412.nc sha1:831c42c3b2ba443c255150289a2c725d7f3e5838 ilamb/WOA/thetao_mon_WOA_A5B4_gn_200501-201412.nc sha1:86d9056208291d76233e65b26c658c1fa54c3ea6 ilamb/RAPID/amoc_mon_RAPID_BE_NA_200404-202302.nc sha1:3efe773e5c2a3c832977791ff7fd9cb9f473fe65 -ilamb/NOAA/ohc_yr_OHC_BE_gm_200506-202406.nc sha1:a918799d8e24e4f0015b9047a74d470ae9f0445c diff --git a/packages/climate-ref-ilamb/src/climate_ref_ilamb/standard.py b/packages/climate-ref-ilamb/src/climate_ref_ilamb/standard.py index 0a1c4ec9f..63f72b3ef 100644 --- a/packages/climate-ref-ilamb/src/climate_ref_ilamb/standard.py +++ b/packages/climate-ref-ilamb/src/climate_ref_ilamb/standard.py @@ -1,3 +1,4 @@ +from collections.abc import Mapping, Sequence from pathlib import Path from typing import Any @@ -20,7 +21,8 @@ ExecutionDefinition, ExecutionResult, ) -from climate_ref_core.esgf import CMIP6Request, CMIP7Request +from climate_ref_core.esgf import CMIP6Request, CMIP7Request, RegistryRequest +from climate_ref_core.esgf.obs4mips import Obs4MIPsRequest from climate_ref_core.metric_values.typing import SeriesMetricValue from climate_ref_core.pycmec.metric import CMECMetric from climate_ref_core.pycmec.output import CMECOutput, OutputCV @@ -213,6 +215,195 @@ def _build_cmec_bundle(df: pd.DataFrame) -> dict[str, Any]: return bundle +def _build_cmip_data_requirement( # noqa: PLR0913 + source_type: SourceDatasetType, + filters: dict[str, Any], + group_by: tuple[str, ...], + primary_variable_ids: tuple[str, ...], + relationship_variable_ids: tuple[str, ...], + is_land: bool, +) -> DataRequirement: + """ + Build a CMIP data requirement with shared constraint logic. + + The constraints (RequireFacets for primary/relationship variables and + AddSupplementaryDataset for land/ocean ancillary data) are identical + for CMIP6 and CMIP7 except for the ``source_type`` parameter. + + Parameters + ---------- + source_type + CMIP6 or CMIP7 + filters + Facet filters specific to this source type + group_by + Columns to group executions by + primary_variable_ids + Variables the diagnostic requires (primary + alternates + related) + relationship_variable_ids + Variables used in relationship analyses + is_land + Whether this is a land diagnostic (determines supplementary variables) + + Returns + ------- + : + A DataRequirement for the given CMIP source type + """ + constraints: list[RequireFacets | AddSupplementaryDataset] = [ + RequireFacets( + "variable_id", + primary_variable_ids, + operator="any", + ), + ] + + if relationship_variable_ids: + constraints.append( + RequireFacets( + "variable_id", + required_facets=relationship_variable_ids, + ) + ) + + if is_land: + constraints.extend( + [ + AddSupplementaryDataset.from_defaults("areacella", source_type), + AddSupplementaryDataset.from_defaults("sftlf", source_type), + ] + ) + else: + constraints.extend( + [ + AddSupplementaryDataset.from_defaults("volcello", source_type), + AddSupplementaryDataset.from_defaults("areacello", source_type), + AddSupplementaryDataset.from_defaults("sftof", source_type), + ] + ) + + return DataRequirement( + source_type=source_type, + filters=(FacetFilter(facets=filters),), + constraints=tuple(constraints), + group_by=group_by, + ) + + +def _build_test_data_spec( # noqa: PLR0913 + all_variable_ids: tuple[str, ...], + registry_file: str, + test_source_id: str, + is_land: bool, + obs_filters: Mapping[str, tuple[str, ...]], + obs_source: str | None = "obs4mips", +) -> TestDataSpecification: + """ + Build a TestDataSpecification for an ILAMB diagnostic. + + Parameters + ---------- + all_variable_ids + All variable IDs used by the diagnostic (primary + alternate + related + relationships) + registry_file + ILAMB registry name ("ilamb", "ilamb-test", or "iomb") + test_source_id + CMIP source_id to use in test cases (e.g. "CanESM5") + is_land + Whether this is a land diagnostic (determines supplementary variables) + obs_filters + Filters extracted from dict-based sources. + If non-empty the test-case will search for non-ilamb datasets. + obs_source + Which source to use for obs4MIPs data ("obs4ref" or "obs4mips"). + Determines if we fetch from ESGF or use the pre-fetched obs4REF registry. + Ignored if no obs_filters provided. + + Returns + ------- + : + Test data specification with cmip6 and cmip7 test cases + """ + if is_land: + supplementary_vars = ["areacella", "sftlf"] + else: + supplementary_vars = ["volcello", "areacello", "sftof"] + test_variable_ids = tuple(sorted(set(all_variable_ids) | set(supplementary_vars))) + + test_branded_names = sorted( + set( + _get_branded_variable( + tuple(test_variable_ids), + registry_file, + ) + ) + ) + + obs4mips_requests: tuple[RegistryRequest | Obs4MIPsRequest, ...] = () + if obs_filters and obs_source is not None: + slug = obs_filters.get("source_id", ["obs4mips"])[0] + if obs_source == "obs4ref": + obs4mips_requests = ( + RegistryRequest( + slug=slug, + registry_name="obs4ref", + facets=obs_filters, # type: ignore + source_type="obs4MIPs", + ), + ) + elif obs_source == "obs4mips": + obs4mips_requests = ( + Obs4MIPsRequest( + slug=slug, + facets=obs_filters, # type: ignore + ), + ) + else: + raise ValueError(f"Invalid obs_source: {obs_source}") + + return TestDataSpecification( + test_cases=( + TestCase( + name="cmip6", + description="Test with CMIP6 data.", + requests=( + CMIP6Request( + slug="cmip6", + facets={ + "experiment_id": "historical", + "source_id": test_source_id, + "variable_id": test_variable_ids, + "frequency": ("fx", "mon"), + }, + remove_ensembles=True, + ), + *obs4mips_requests, + ), + ), + TestCase( + name="cmip7", + description="Test with CMIP7 data.", + requests=( + CMIP7Request( + slug="cmip7", + facets={ + "experiment_id": "historical", + "source_id": test_source_id, + "variable_id": test_variable_ids, + "branded_variable": test_branded_names, + "variant_label": "r1i1p1f1", + "frequency": ["fx", "mon"], + "region": "glb", + }, + remove_ensembles=True, + ), + *obs4mips_requests, + ), + ), + ) + ) + + def _set_ilamb3_options(registry: pooch.Pooch, registry_file: str) -> None: """ Set options for ILAMB based on which registry file is being used. @@ -228,6 +419,9 @@ def _set_ilamb3_options(registry: pooch.Pooch, registry_file: str) -> None: # source_id/member_id/grid_label at a time, relax the groupby option here so # these measures are part of the dataframe in ilamb3. ilamb3.conf.set(comparison_groupby=["source_id", "grid_label"]) + # You can control how models are known in the results, drop some facets for + # legibility. + ilamb3.conf.set(model_name_facets=["source_id"]) def _load_csv_and_merge(output_directory: Path) -> pd.DataFrame: @@ -249,13 +443,11 @@ def __init__( self, registry_file: str, metric_name: str, - sources: dict[str, str], + sources: dict[str, str | dict[str, str]], **ilamb_kwargs: Any, ): # Setup the diagnostic - if len(sources) != 1: - raise ValueError("Only single source ILAMB diagnostics have been implemented.") - self.variable_id = next(iter(sources.keys())) + self.variable_id = ilamb_kwargs.get("analysis_variable", next(iter(sources.keys()))) if "sources" not in ilamb_kwargs: # pragma: no cover ilamb_kwargs["sources"] = sources if "relationships" not in ilamb_kwargs: @@ -292,111 +484,85 @@ def __init__( # Determine realm/region for CMIP7 filter based on registry is_land = registry_file in ("ilamb", "ilamb-test") - # CMIP6 data requirement - cmip6_requirement = DataRequirement( + relationship_variable_ids = tuple(ilamb_kwargs.get("relationships", {}).keys()) + + # Create the data requirement for the dataset under test + cmip6_requirement = _build_cmip_data_requirement( source_type=SourceDatasetType.CMIP6, - filters=( - FacetFilter( - facets={ - "variable_id": all_variable_ids, - "frequency": "mon", - "experiment_id": ("historical", "land-hist"), - "table_id": ( - "AERmonZ", - "Amon", - "CFmon", - "Emon", - "EmonZ", - "LImon", - "Lmon", - "Omon", - "SImon", - ), - } - ), - ), - constraints=( - RequireFacets( - "variable_id", - primary_variable_ids, - operator="any", + filters={ + "variable_id": all_variable_ids, + "frequency": "mon", + "experiment_id": ("historical", "land-hist"), + "table_id": ( + "AERmonZ", + "Amon", + "CFmon", + "Emon", + "EmonZ", + "LImon", + "Lmon", + "Omon", + "SImon", ), - *( - [ - RequireFacets( - "variable_id", - required_facets=tuple(ilamb_kwargs.get("relationships", {}).keys()), - ) - ] - if "relationships" in ilamb_kwargs - else [] - ), - *( - ( - AddSupplementaryDataset.from_defaults("areacella", SourceDatasetType.CMIP6), - AddSupplementaryDataset.from_defaults("sftlf", SourceDatasetType.CMIP6), - ) - if is_land - else ( - AddSupplementaryDataset.from_defaults("volcello", SourceDatasetType.CMIP6), - AddSupplementaryDataset.from_defaults("areacello", SourceDatasetType.CMIP6), - AddSupplementaryDataset.from_defaults("sftof", SourceDatasetType.CMIP6), - ) - ), - ), + }, group_by=("experiment_id", "source_id", "member_id", "grid_label"), + primary_variable_ids=primary_variable_ids, + relationship_variable_ids=relationship_variable_ids, + is_land=is_land, ) - # CMIP7 data requirement - cmip7_requirement = DataRequirement( + cmip7_requirement = _build_cmip_data_requirement( source_type=SourceDatasetType.CMIP7, - filters=( - FacetFilter( - facets={ - "branded_variable": branded_variables, - "frequency": "mon", - "experiment_id": ("historical", "land-hist"), - "region": "glb", - } - ), - ), - constraints=( - RequireFacets( - "variable_id", - primary_variable_ids, - operator="any", - ), - *( - [ - RequireFacets( - "variable_id", - required_facets=tuple(ilamb_kwargs.get("relationships", {}).keys()), - ) - ] - if "relationships" in ilamb_kwargs - else [] - ), - *( - ( - AddSupplementaryDataset.from_defaults("areacella", SourceDatasetType.CMIP7), - AddSupplementaryDataset.from_defaults("sftlf", SourceDatasetType.CMIP7), - ) - if is_land - else ( - AddSupplementaryDataset.from_defaults("volcello", SourceDatasetType.CMIP7), - AddSupplementaryDataset.from_defaults("areacello", SourceDatasetType.CMIP7), - AddSupplementaryDataset.from_defaults("sftof", SourceDatasetType.CMIP7), - ) - ), - ), + filters={ + "branded_variable": branded_variables, + "frequency": "mon", + "experiment_id": ("historical", "land-hist"), + "region": "glb", + }, group_by=("experiment_id", "source_id", "variant_label", "grid_label"), + primary_variable_ids=primary_variable_ids, + relationship_variable_ids=relationship_variable_ids, + is_land=is_land, ) - self.data_requirements = ( - (cmip6_requirement,), - (cmip7_requirement,), + # obs4MIPs data requirement, normally ilamb3 expects the `sources` to + # resolve to keys in one of its data registries. If instead we find a + # dictionary, then assume that these keys are meant to be keywords in a + # REF data requirement. + # obs_source key is used to determine whether to fetch from ESGF + # or use the pre-fetched obs4REF registry. + filters: dict[str, tuple[str, ...]] = {} + obs_source = None + for _, source in sources.items(): + if isinstance(source, dict): + obs_source = source.pop("obs_source", None) + for key, val in source.items(): + if key not in filters: + filters[key] = () + filters[key] += (val,) + obs4mips_requirement = ( + DataRequirement( + source_type=SourceDatasetType.obs4MIPs, + filters=(FacetFilter(facets=filters),), + group_by=tuple(filters.keys()), + ) + if filters + else None ) + data_requirements: Sequence[Sequence[DataRequirement]] + if obs4mips_requirement is None: + data_requirements = ( + (cmip6_requirement,), + (cmip7_requirement,), + ) + else: + data_requirements = ( + (cmip6_requirement, obs4mips_requirement), + (cmip7_requirement, obs4mips_requirement), + ) + self.data_requirements = data_requirements + self.facets = ( "experiment_id", "source_id", @@ -407,83 +573,13 @@ def __init__( "statistic", ) - # Build test data spec for CMIP6 and CMIP7 test cases - supplementary_vars: list[str] - if is_land: - supplementary_vars = ["areacella", "sftlf"] - else: - supplementary_vars = ["volcello", "areacello", "sftof"] - test_variable_ids = sorted(set(all_variable_ids) | set(supplementary_vars)) - - # Build branded variable names for the test spec (include supplementary) - test_branded_names = sorted( - set( - _get_branded_variable( - tuple(test_variable_ids), - registry_file, - ) - ) - ) - - self.test_data_spec = TestDataSpecification( - test_cases=( - TestCase( - name="cmip6", - description="Test with CMIP6 data.", - requests=( - CMIP6Request( - slug="cmip6", - facets={ - "experiment_id": "historical", - "source_id": test_source_id, - "variable_id": test_variable_ids, - "frequency": ["fx", "mon"], - }, - remove_ensembles=True, - ), - ), - ), - TestCase( - name="cmip7", - description="Test with CMIP7 data.", - requests=( - CMIP7Request( - slug="cmip7", - facets={ - "experiment_id": "historical", - "source_id": test_source_id, - "variable_id": test_variable_ids, - "branded_variable": test_branded_names, - "variant_label": "r1i1p1f1", - "frequency": ["fx", "mon"], - "region": "glb", - }, - remove_ensembles=True, - ), - # Separate request for volcello without variant_label - # constraint. Some models (e.g. CanESM5) publish volcello - # only for certain physics versions (p2), so the main - # request pinned to r1i1p1f1 won't find it. - *( - ( - CMIP7Request( - slug="cmip7-volcello", - facets={ - "experiment_id": "historical", - "source_id": test_source_id, - "variable_id": "volcello", - "frequency": "fx", - "region": "glb", - }, - remove_ensembles=True, - ), - ) - if not is_land - else () - ), - ), - ), - ) + self.test_data_spec = _build_test_data_spec( + all_variable_ids=all_variable_ids, + registry_file=registry_file, + test_source_id=test_source_id, + is_land=is_land, + obs_filters=filters, + obs_source=obs_source, ) # Setup ILAMB data and options @@ -498,8 +594,32 @@ def execute(self, definition: ExecutionDefinition) -> None: Run the ILAMB standard analysis. """ _set_ilamb3_options(self.registry, self.registry_file) - ref_datasets = self.ilamb_data.datasets.set_index(self.ilamb_data.slug_column) - + # Temporary hack of the ilamb3 inputs while we still need to refer to + # data not yet available in obs4{MIPs,REF}. This logic allows for + # DataRequirement filters to be added as a 'source' in the ilamb + # configure file. If a dictionary instead of a string was found, we + # populate an obs4MIPs requirement. + if SourceDatasetType.obs4MIPs in definition.datasets: + # ilamb3 will expect the reference dataset dataframe to have a `key` + # column that uniquely describes each dataset. Create one using the + # `instance_id` and an integer and then modify the ilamb3 configure + # so that it finds the proper data. + ref_datasets = definition.datasets[SourceDatasetType.obs4MIPs].datasets + ref_datasets = ref_datasets.reset_index() + ref_datasets["key"] = ref_datasets["instance_id"] + ref_datasets.index.astype(str) + for instance_id, df in ref_datasets.groupby("instance_id"): + variable_id = df["variable_id"].unique()[0] + self.ilamb_kwargs["sources"][variable_id] = f"{instance_id}*" + else: + # If the data is not ingested yet but in a registry, we concat the + # obs4REF registries to the ilamb one so that any key may be used + # from either. Eventually (?) this can be removed. + ref_datasets = pd.concat( + [ + self.ilamb_data.datasets.set_index(self.ilamb_data.slug_column), + registry_to_collection(dataset_registry_manager["obs4ref"]).datasets.set_index("key"), + ] + ) cmip_source = _get_cmip_source_type(definition.datasets) model_datasets = definition.datasets[cmip_source].datasets @@ -523,6 +643,11 @@ def execute(self, definition: ExecutionDefinition) -> None: if available_alternates and self.variable_id in model_datasets["variable_id"].values: model_datasets = model_datasets[model_datasets["variable_id"] != self.variable_id] + # In ilamb3, this is run with all the models that we know about to + # create different colors. For REF this will at least make the model + # line color not be black + run.set_model_colors(model_datasets) + # Run ILAMB in a single-threaded mode to avoid issues with multithreading (#394) with dask.config.set(scheduler="synchronous"): run.run_single_block( diff --git a/uv.lock b/uv.lock index bff55b3a2..84e6353d2 100644 --- a/uv.lock +++ b/uv.lock @@ -861,7 +861,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "climate-ref-core", editable = "packages/climate-ref-core" }, - { name = "ilamb3", specifier = ">=2025.9.9,<2025.12" }, + { name = "ilamb3", specifier = ">=2026.2.19" }, { name = "scipy", specifier = "<1.16" }, { name = "xarray", specifier = "<2025.11" }, ] @@ -1718,7 +1718,7 @@ wheels = [ [[package]] name = "ilamb3" -version = "2025.9.9" +version = "2026.2.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cartopy" }, @@ -1733,14 +1733,17 @@ dependencies = [ { name = "pandas" }, { name = "pint-xarray" }, { name = "pooch" }, + { name = "pyarrow" }, { name = "pyyaml" }, { name = "scipy" }, { name = "statsmodels" }, + { name = "tqdm" }, + { name = "typer" }, { name = "xarray" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/92/4026b03878bc64cb46d4cf8f87fe66ab8472f7040a1c7b13f45c8c764a39/ilamb3-2025.9.9.tar.gz", hash = "sha256:fd0c3e55c322dbf0ad74ea82c26c10543e507cab64cd89c7a0b83aa894062987", size = 430926, upload-time = "2025-09-09T19:57:28.051Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/1b/1fbab0ecd53126c1771df7cef3574a52019eb8f24ea6e6e33033765259fb/ilamb3-2026.2.19.tar.gz", hash = "sha256:28c2aff287309c13932d300702819ddd799acb06ee3a989cc3f676ba6ad2f86b", size = 507980, upload-time = "2026-02-19T20:49:16.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/a4/b2e5838f73306f639ffe5c621f46f2ef1841206c5b9f88609875d5142446/ilamb3-2025.9.9-py3-none-any.whl", hash = "sha256:8b091f59798e663ea4055e606b66c938d850a26191b3425d6e30eb2f89857a0a", size = 98708, upload-time = "2025-09-09T19:57:27.04Z" }, + { url = "https://files.pythonhosted.org/packages/30/c9/15c6ee07a2e272ec9ffc76f0f04d1d86c717615b39d864eebce9818c0efc/ilamb3-2026.2.19-py3-none-any.whl", hash = "sha256:dbfbef38c11b13d832670ea7723ed319a24df2e736ceae78d85c1f78dcdb0c1c", size = 125871, upload-time = "2026-02-19T20:49:18.041Z" }, ] [[package]]