Companion notebook: remote JSON/Kerchunk CMIP access#
Demonstration companion to New Tools for Accessing CMIP Data at NERSC and Beyond#
Climate and Weather Seminar Series (CWSS)#
Sasha Ames, Stephen Po-Chedley & Tom Vo#
Lawrence Livermore National Laboratory
September 24, 2026 · 11:00 AM
This work is performed under the auspices of the U.S. Department of Energy by Lawrence Livermore National Laboratory under Contract No. DE-AC52-07NA27344.
Notebook overview#
This demonstration uses Kerchunk references to access CMIP6 data remotely, processes it with xCDAT, and compares that workflow with a local NERSC analysis. It is the remote JSON/Kerchunk demonstration component of the companion CWSS presentation.
Select CMIP6 surface-temperature datasets and ECS metadata
Open remote Kerchunk references and calculate warming trends
Repeat the calculation with local NERSC data
Compare scientific results and illustrative end-to-end timings
Environment and access requirements#
Create a conda environment containing xcdat, matplotlib, cartopy, fsspec, aiohttp, requests, zarr, jupyter, and ipykernel. The local NERSC comparison additionally requires xsearch, which is not included with xCDAT:
conda create -n cwss-kerchunk -c conda-forge xcdat matplotlib cartopy fsspec aiohttp requests zarr jupyter ipykernel
conda activate cwss-kerchunk
python -m pip install git+https://github.com/PCMDI/xsearch.git
python -m ipykernel install --user --name cwss-kerchunk --display-name "CWSS Kerchunk"
The remote portion uses ORNL-hosted Kerchunk references. The local portion requires NERSC data access and xsearch. Read the accompanying README.md for input provenance and reproducibility notes.
Imports#
[20]:
import json
import time
from collections.abc import Mapping, Sequence
from typing import Any, Literal
import cartopy.crs as ccrs
import fsspec
import matplotlib.pyplot as plt
import numpy as np
import pooch
import xarray as xr
import xcdat as xc
from cartopy.util import add_cyclic_point
Kerchunk workflow helpers#
These helper functions are defined in the notebook so the demonstration can run as a self-contained file.
[21]:
KERCHUNK_SITE_URLS = {
"nersc": "https://g-eba899.6b7bd8.0ec8.data.globus.org/kerchunk",
"ornl": "https://esgf-node.ornl.gov/thredds/fileServer/user_pub_work/kerchunk",
}
def find_json_files(
catalog: Mapping[str, Mapping[str, Any]], **facets: Any
) -> dict[str, Mapping[str, Any]]:
"""Return catalog entries whose facets match every supplied value."""
return {
path: metadata
for path, metadata in catalog.items()
if all(metadata.get(facet) == value for facet, value in facets.items())
}
def open_kerchunk_reference(
reference_file: str, site: Literal["nersc", "ornl"] = "ornl"
) -> xr.Dataset:
"""Open a remotely hosted Kerchunk reference as an xarray dataset."""
try:
reference_url = f"{KERCHUNK_SITE_URLS[site]}/{reference_file}"
except KeyError as error:
supported_sites = ", ".join(KERCHUNK_SITE_URLS)
raise ValueError(
f"Unsupported Kerchunk site {site!r}. Choose one of: {supported_sites}."
) from error
filesystem = fsspec.filesystem(
"reference",
fo=reference_url,
remote_options={"asynchronous": True},
remote_protocol="https",
asynchronous=True,
)
return xr.open_dataset(
filesystem.get_mapper(""), engine="zarr", consolidated=False
)
def calculate_model_trends(
dataset: xr.Dataset,
variable_id: str,
target_grid: xr.Dataset,
expected_year_count: int,
) -> tuple[float, xr.DataArray]:
"""Calculate native-grid global and regridded cell-wise trends in K decade-1."""
dataset = dataset.bounds.add_missing_bounds()
if "height" in dataset.coords:
dataset = dataset.drop_vars("height")
annual_dataset = dataset.temporal.group_average(variable_id, freq="year")
regridded_dataset = annual_dataset.regridder.horizontal(
variable_id, target_grid, tool="regrid2"
)
decimal_year = regridded_dataset.time.dt.decimal_year
if len(decimal_year) != expected_year_count:
raise ValueError(
"Annual time coordinate has "
f"{len(decimal_year)} samples; expected {expected_year_count}."
)
regridded_dataset = regridded_dataset.assign_coords(time=decimal_year)
trend_dataset = regridded_dataset[variable_id].polyfit(dim="time", deg=1)
grid_cell_trend = trend_dataset.polyfit_coefficients.sel(degree=1) * 10
global_mean = annual_dataset.spatial.average(variable_id)[variable_id]
global_trend, _ = np.polyfit(decimal_year, global_mean.values, 1)
return float(global_trend * 10), grid_cell_trend
def select_available_models(
catalog: Mapping[str, Mapping[str, Any]],
ecs_data: Mapping[str, float],
excluded_models: Sequence[str] = (),
) -> list[str]:
"""Return sorted catalog models with ECS data that are not excluded."""
excluded_model_set = set(excluded_models)
catalog_models = {metadata["model"] for metadata in catalog.values()}
return sorted((catalog_models & set(ecs_data)) - excluded_model_set)
The imports below support the remote workflow. xsearch is imported in the NERSC-local section so the remote portion can run without that optional dependency.
Analysis parameters#
[22]:
ANALYSIS_PERIOD = slice("1981-01-01", "2014-12-30")
LATITUDES = np.arange(-88.75, 90, 2.5)
LONGITUDES = np.arange(1.25, 360, 2.5)
EXPECTED_YEAR_COUNT = 34
TIMING_RUNS = 3
VARIABLE_ID = "tas"
EXCLUDED_MODELS = {"CIESM", "GFDL-CM4", "GFDL-ESM4", "NorCPM1"}
KERCHUNK_CATALOG_URL = (
"https://raw.githubusercontent.com/xCDAT/xcdat-data/"
"e31bf6cdfd478550e9a284e6d17ef35edce5ee03/resources/kerchunk_list.json"
)
KERCHUNK_CATALOG_HASH = "sha256:fe1137950015073fa6bd586d61bfda426fad6053284a9e74d7034b451e663028"
ECS_DATA_URL = (
"https://raw.githubusercontent.com/xCDAT/xcdat-data/"
"a727575e259499ba21d596d7ddabd8433250aac7/resources/ecsdata.json"
)
ECS_DATA_HASH = "sha256:4c06aaa54fbdf225347bc16482ab3009724246a72508578fa51832060eb481b4"
Load equilibrium climate sensitivity data#
[23]:
ecs_data_path = pooch.retrieve(
url=ECS_DATA_URL,
known_hash=ECS_DATA_HASH,
path=pooch.os_cache("xcdat"),
fname="cwss-2026-ecsdata.json",
)
with open(ecs_data_path) as file:
ecs_data = json.load(file)
Create a common target grid#
[24]:
latitude_axis = xc.create_axis("lat", LATITUDES)
longitude_axis = xc.create_axis("lon", LONGITUDES)
target_grid = xc.create_grid(x=longitude_axis, y=latitude_axis)
Retrieve and select remote CMIP6 references#
Download the pinned Kerchunk catalog once with Pooch, then filter it to historical monthly near-surface air temperature (tas) datasets. Retain models with ECS metadata and omit models whose available data do not support this demonstration.
[25]:
catalog_path = pooch.retrieve(
url=KERCHUNK_CATALOG_URL,
known_hash=KERCHUNK_CATALOG_HASH,
path=pooch.os_cache("xcdat"),
fname="cwss-2026-kerchunk_list.json",
)
with open(catalog_path) as file:
kerchunk_catalog = json.load(file)
remote_catalog = find_json_files(
kerchunk_catalog,
experiment="historical",
variable=VARIABLE_ID,
frequency="mon",
cmipTable="Amon",
)
model_names = select_available_models(
remote_catalog, ecs_data, excluded_models=EXCLUDED_MODELS
)
print(f"Selected {len(model_names)} models for analysis.")
Selected 38 models for analysis.
Remote Kerchunk workflow#
Run the processing workflow three times after the catalog is available locally and report the median duration. The timing therefore measures remote reference access, data loading, and xCDAT processing without including the one-time catalog download; it remains sensitive to network and cache conditions.
[26]:
# Timing begins in the processing cell.
Calculate global model trends and trend maps#
For each model, load the satellite-era data, add CF bounds when needed, calculate annual means, regrid to a shared 2.5° grid, and calculate both global and grid-cell trends.
[27]:
def process_remote_models():
remote_global_trends = {}
remote_trend_maps = []
processed_model_names = []
model_sources = {}
skipped_remote_models = {}
for model_name in model_names:
dataset = None
model_entries = sorted(
(
metadata
for metadata in remote_catalog.values()
if metadata["model"] == model_name
),
key=lambda metadata: metadata["json_file"],
)
metadata = model_entries[0]
try:
dataset = open_kerchunk_reference(metadata["json_file"], site="ornl")
dataset = dataset.sel(time=ANALYSIS_PERIOD).load()
global_trend, trend_map = calculate_model_trends(
dataset, VARIABLE_ID, target_grid, EXPECTED_YEAR_COUNT
)
except (OSError, ValueError, KeyError) as error:
skipped_remote_models[model_name] = str(error)
continue
finally:
if dataset is not None:
dataset.close()
remote_global_trends[model_name] = global_trend
remote_trend_maps.append(trend_map)
processed_model_names.append(model_name)
model_sources[model_name] = (metadata["member"], metadata["version"])
return (
remote_global_trends,
remote_trend_maps,
processed_model_names,
model_sources,
skipped_remote_models,
)
remote_elapsed_seconds = []
for _ in range(TIMING_RUNS):
start = time.perf_counter()
remote_results = process_remote_models()
remote_elapsed_seconds.append(time.perf_counter() - start)
(
remote_global_trends,
remote_trend_maps,
processed_model_names,
model_sources,
skipped_remote_models,
) = remote_results
remote_elapsed_median_seconds = float(np.median(remote_elapsed_seconds))
print(
f"Processed {len(processed_model_names)} models; "
f"skipped {len(skipped_remote_models)} models."
)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
Processed 38 models; skipped 0 models.
[28]:
remote_models = xr.DataArray(
processed_model_names, dims="model", coords={"model": processed_model_names}
)
remote_trend_collection = xr.concat(remote_trend_maps, dim=remote_models)
Relate global warming trends to equilibrium climate sensitivity#
The scatter plot compares each model’s global warming trend with its equilibrium climate sensitivity (ECS). This diagnostic is inspired by multimodel analyses such as Tokarska et al. (2020).
[29]:
remote_trends = np.array(list(remote_global_trends.values()))
remote_ecs = np.array([ecs_data[model_name] for model_name in remote_global_trends])
slope, intercept = np.polyfit(remote_trends, remote_ecs, 1)
fit_x = np.linspace(remote_trends.min(), remote_trends.max(), 100)
correlation = np.corrcoef(remote_trends, remote_ecs)[0, 1]
fig, axis = plt.subplots(figsize=(7, 5))
axis.plot(remote_trends, remote_ecs, "o", color="black", markerfacecolor="none")
axis.plot(fit_x, fit_x * slope + intercept, "k--")
axis.set(
xlabel="Global surface warming trend [K decade$^{-1}$]",
ylabel="Equilibrium climate sensitivity [K]",
title="Remote Kerchunk analysis",
)
axis.text(0.03, 0.95, f"r = {correlation:.2f}\nn = {len(remote_trends)}", transform=axis.transAxes, va="top")
plt.show()
Plot the multimodel mean warming trend#
[30]:
fig, axis = plt.subplots(figsize=(10, 6), subplot_kw={"projection": ccrs.Robinson()})
cyclic_data, cyclic_longitude = add_cyclic_point(
remote_trend_collection.mean(dim="model"), coord=longitude_axis[0]
)
contour = axis.contourf(
cyclic_longitude,
latitude_axis[0],
cyclic_data,
np.arange(-1, 1, 0.1),
transform=ccrs.PlateCarree(),
cmap=plt.cm.RdBu_r,
extend="both",
)
axis.coastlines(resolution="110m", color="black", linewidth=1)
fig.colorbar(contour, ax=axis, orientation="horizontal", pad=0.05, shrink=0.7, label="Warming trend [K decade$^{-1}$]")
axis.set_title("Remote Kerchunk: multimodel mean warming trend")
plt.show()
Record remote workflow timing#
[31]:
remote_elapsed_time = time.strftime(
"%M:%S", time.gmtime(remote_elapsed_median_seconds)
)
remote_elapsed_times = [
time.strftime("%M:%S", time.gmtime(seconds))
for seconds in remote_elapsed_seconds
]
print(
f"Remote workflow median ({TIMING_RUNS} runs): {remote_elapsed_time} "
f"to load and process data (runs: {', '.join(remote_elapsed_times)})."
)
Remote workflow median (3 runs): 03:20 to load and process data (runs: 03:20, 03:13, 03:21).
Local NERSC workflow#
Repeat the same analysis against locally accessible NERSC datasets. This section requires NERSC access and xsearch; it is not expected to run in a general public environment.
[32]:
# Timing begins after local dataset discovery.
Discover local CMIP6 data with xsearch#
[33]:
import xsearch as xs
local_catalog = xs.findPaths(
experiment="historical",
variable=VARIABLE_ID,
frequency="mon",
cmipTable="Amon",
mip_era="CMIP6",
)
Calculate local global model trends and trend maps#
Use the same xCDAT processing helper as the remote workflow, while matching each local dataset to the member and version selected from the remote catalog.
[34]:
def process_local_models():
local_global_trends = {}
local_trend_maps = []
local_model_names = []
skipped_local_models = {}
for model_name, (member, version) in model_sources.items():
dataset = None
local_entries = sorted(
(
(path, metadata)
for path, metadata in local_catalog.items()
if metadata["model"] == model_name
and metadata["json_file"] is not None
and metadata["member"] == member
and metadata["version"] == version
),
key=lambda entry: entry[0],
)
if not local_entries:
skipped_local_models[model_name] = "No matching local dataset"
continue
try:
dataset = xc.open_mfdataset(local_entries[0][0])
dataset = dataset.sel(time=ANALYSIS_PERIOD).load()
global_trend, trend_map = calculate_model_trends(
dataset, VARIABLE_ID, target_grid, EXPECTED_YEAR_COUNT
)
except (OSError, ValueError, KeyError) as error:
skipped_local_models[model_name] = str(error)
continue
finally:
if dataset is not None:
dataset.close()
local_global_trends[model_name] = global_trend
local_trend_maps.append(trend_map)
local_model_names.append(model_name)
return (
local_global_trends,
local_trend_maps,
local_model_names,
skipped_local_models,
)
local_elapsed_seconds = []
for _ in range(TIMING_RUNS):
start = time.perf_counter()
local_results = process_local_models()
local_elapsed_seconds.append(time.perf_counter() - start)
(
local_global_trends,
local_trend_maps,
local_model_names,
skipped_local_models,
) = local_results
local_elapsed_median_seconds = float(np.median(local_elapsed_seconds))
print(
f"Processed {len(local_model_names)} models; "
f"skipped {len(skipped_local_models)} models."
)
if not local_trend_maps:
raise RuntimeError(
"No local model trends were calculated. Inspect "
"skipped_local_models for the per-model failure reasons."
)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
/global/homes/v/vo13/miniforge3/envs/cwss-kerchunk/lib/python3.14/site-packages/xarray/conventions.py:205: SerializationWarning: variable 'tas' has multiple fill values {np.float32(1e+20), np.float64(1e+20)} defined, decoding all values to NaN.
var = coder.decode(var, name=name)
Processed 38 models; skipped 0 models.
[35]:
local_models = xr.DataArray(
local_model_names, dims="model", coords={"model": local_model_names}
)
local_trend_collection = xr.concat(local_trend_maps, dim=local_models)
Compare local warming trends with equilibrium climate sensitivity#
[36]:
local_trends = np.array(list(local_global_trends.values()))
local_ecs = np.array([ecs_data[model_name] for model_name in local_global_trends])
slope, intercept = np.polyfit(local_trends, local_ecs, 1)
fit_x = np.linspace(local_trends.min(), local_trends.max(), 100)
correlation = np.corrcoef(local_trends, local_ecs)[0, 1]
fig, axis = plt.subplots(figsize=(7, 5))
axis.plot(local_trends, local_ecs, "o", color="black", markerfacecolor="none")
axis.plot(fit_x, fit_x * slope + intercept, "k--")
axis.set(
xlabel="Global surface warming trend [K decade$^{-1}$]",
ylabel="Equilibrium climate sensitivity [K]",
title="Local NERSC analysis",
)
axis.text(0.03, 0.95, f"r = {correlation:.2f}\nn = {len(local_trends)}", transform=axis.transAxes, va="top")
plt.show()
Plot the local multimodel mean warming trend#
[37]:
fig, axis = plt.subplots(figsize=(10, 6), subplot_kw={"projection": ccrs.Robinson()})
cyclic_data, cyclic_longitude = add_cyclic_point(
local_trend_collection.mean(dim="model"), coord=longitude_axis[0]
)
contour = axis.contourf(
cyclic_longitude,
latitude_axis[0],
cyclic_data,
np.arange(-1, 1, 0.1),
transform=ccrs.PlateCarree(),
cmap=plt.cm.RdBu_r,
extend="both",
)
axis.coastlines(resolution="110m", color="black", linewidth=1)
fig.colorbar(contour, ax=axis, orientation="horizontal", pad=0.05, shrink=0.7, label="Warming trend [K decade$^{-1}$]")
axis.set_title("Local NERSC: multimodel mean warming trend")
plt.show()
Compare workflow timings#
Each timing is the median of three runs of data loading and analysis after catalog discovery or reference selection. They are examples from one environment, not portable performance benchmarks.
[38]:
local_elapsed_time = time.strftime(
"%M:%S", time.gmtime(local_elapsed_median_seconds)
)
local_elapsed_times = [
time.strftime("%M:%S", time.gmtime(seconds))
for seconds in local_elapsed_seconds
]
print(
f"Remote workflow median ({TIMING_RUNS} runs): {remote_elapsed_time}; "
f"local NERSC workflow median ({TIMING_RUNS} runs): {local_elapsed_time}."
f"\nRemote runs: {', '.join(remote_elapsed_times)}."
f"\nLocal runs: {', '.join(local_elapsed_times)}."
)
Remote workflow median (3 runs): 03:20; local NERSC workflow median (3 runs): 02:40.
Remote runs: 03:20, 03:13, 03:21.
Local runs: 03:10, 02:38, 02:40.
Timings can vary between runs due to network conditions, filesystem caching, system load, and remote data-service availability.