From 510d05d6a13e754b62f4a666263f4061ff630679 Mon Sep 17 00:00:00 2001 From: brian-rose Date: Fri, 8 Aug 2025 18:10:44 +0000 Subject: [PATCH 1/3] Add a new multi-model comparison notebook --- myst.yml | 1 + notebooks/foundations/multi-model.ipynb | 662 ++++++++++++++++++++++++ 2 files changed, 663 insertions(+) create mode 100644 notebooks/foundations/multi-model.ipynb diff --git a/myst.yml b/myst.yml index 703219fa..ae8d19cb 100644 --- a/myst.yml +++ b/myst.yml @@ -24,6 +24,7 @@ project: - file: notebooks/foundations/energy-balance-model.ipynb - file: notebooks/foundations/manual-calc.ipynb - file: notebooks/foundations/climkern-calc.ipynb + - file: notebooks/foundations/multi-model.ipynb - title: Appendix children: - file: references.md diff --git a/notebooks/foundations/multi-model.ipynb b/notebooks/foundations/multi-model.ipynb new file mode 100644 index 00000000..587f615e --- /dev/null +++ b/notebooks/foundations/multi-model.ipynb @@ -0,0 +1,662 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "author: brian-rose\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Multi-model feedback comparison" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Overview\n", + "\n", + "The goal of this notebook is to explore inter-model differences in water vapor and lapse rate feedbacks, as well as the partial cancellation between these differences when the two feedbacks are combined. See discussion in {cite:t}`Held:2012a`.\n", + "\n", + ":::{note}\n", + "This notebook is still under construction and will include more verbose detail at some point in the future!\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import climkern as ck\n", + "import intake\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import s3fs\n", + "import fsspec\n", + "import xarray as xr\n", + "import glob\n", + "import importlib.util\n", + "import os\n", + "import cartopy.crs as ccrs\n", + "\n", + "%matplotlib inline\n", + "plt.rcParams[\"figure.dpi\"] = 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download the kernel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ":::{note}\n", + "This follows the same hack as in the [ClimKern notebook](climkern-calc). Hopefully soon we'll have a more graceful way to stream the data directly into ClimKern without needing to save to the local workspace.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "URL = \"https://js2.jetstream-cloud.org:8001/\" # URL for jetstream access\n", + "path = f\"pythia/ClimKern\" # Location of ClimKern\n", + "kernel = \"ERA5\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, read in the data from Jetstream2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Read in data\n", + "# Set up access to jetstream2\n", + "fs = fsspec.filesystem(\"s3\", anon=True, client_kwargs=dict(endpoint_url=URL))\n", + "pattern = f\"s3://{path}/kernels/\"+kernel+f\"/TOA*.nc\"\n", + "\n", + "# Grab the data\n", + "files = sorted(fs.glob(pattern))\n", + "fs.invalidate_cache() # This is necessary to deal with peculiarities of objects served from jetstream2\n", + "\n", + "# Open file and make it Xarray Dataset\n", + "kern = xr.open_dataset(fs.open(files[0]))\n", + "\n", + "# Save path for later\n", + "path_out = files[0].split(kernel+\"/\",1)[1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To save this data in ClimKern's directory, we have to figure out where it is on the\n", + "machine. After that, we will go ahead and save out the kernel as a netCDF in \n", + "ClimKern's local data directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get the package location\n", + "spec = importlib.util.find_spec(\"climkern\")\n", + "package_dir = os.path.dirname(spec.origin)\n", + "\n", + "# print(f\"The package directory is: {package_dir}\")\n", + "\n", + "# Define the path where you want to save the netCDF file within the package directory\n", + "netcdf_path = os.path.join(package_dir,\"data/kernels\",kernel,path_out)\n", + "\n", + "# Ensure the directory exists\n", + "os.makedirs(os.path.dirname(netcdf_path), exist_ok=True)\n", + "\n", + "# Save the dataset as a netCDF file\n", + "kern.to_netcdf(netcdf_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prepare the CMIP6 Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To start, we will need some CMIP6 data to calculate feedbacks. We will use the\n", + "preindustrial control and 4×CO$_2$ experiments from two models: CESM2 and GFDL CM4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Make a list of variables and experiments we need\n", + "var_list = [\"ta\",\"ts\",\"ps\",\"hus\"]\n", + "exp_list = [\"piControl\",\"abrupt-4xCO2\"]\n", + "\n", + "# Specify data location, open it\n", + "cat_url = \"https://storage.googleapis.com/cmip6/pangeo-cmip6.json\"\n", + "col = intake.open_esm_datastore(cat_url)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cat = col.search(experiment_id=exp_list,source_id=[\"CESM2\",\"GFDL-CM4\"],variable_id=var_list,\n", + " table_id=\"Amon\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Convert to a dictionary of Xarray Datasets\n", + "ds_dict = cat.to_dataset_dict(xarray_open_kwargs={\"consolidated\": True, \"decode_times\": True, \"use_cftime\": True})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data, especially the preindustrial control simulation that will serve as our\n", + "control climate, is huge. We are going to only use the last 50 years of the control\n", + "and last 30 years of the abrupt 4×CO$_2$ simulation. There are a few extra coordinates\n", + "and/or dimensions we don't need, hence the `squeeze()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Our control simulation\n", + "ctrl_cesm2 = ds_dict[\"CMIP.NCAR.CESM2.piControl.Amon.gn\"].isel(\n", + " time=slice(-600,None)).compute()\n", + "\n", + "# The increase CO2 aka \"perturbed\" simulation\n", + "pert_cesm2 = ds_dict[\"CMIP.NCAR.CESM2.abrupt-4xCO2.Amon.gn\"].isel(\n", + " time=slice(-360,None)).compute()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ctrl_cm4 = ds_dict[\"CMIP.NOAA-GFDL.GFDL-CM4.piControl.Amon.gr1\"].isel(time=slice(-50*12,None)).compute()\n", + "pert_cm4 = ds_dict[\"CMIP.NOAA-GFDL.GFDL-CM4.abrupt-4xCO2.Amon.gr1\"].isel(time=slice(-30*12,None)).compute()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def make_monthly_clim(dataset):\n", + " return dataset.groupby(dataset.time.dt.month).mean(dim=\"time\").rename({\"month\":\"time\"})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ctrl_cesm2 = make_monthly_clim(ctrl_cesm2)\n", + "pert_cesm2 = make_monthly_clim(pert_cesm2)\n", + "ctrl_cm4 = make_monthly_clim(ctrl_cm4)\n", + "pert_cm4 = make_monthly_clim(pert_cm4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Feedback calculations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate surface temperature change" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Take difference between pert and ctrl ts fields\n", + "Δts_cesm2 = pert_cesm2.ts - ctrl_cesm2.ts\n", + "Δts_cm4 = pert_cm4.ts - ctrl_cm4.ts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Planck and lapse rate feedbacks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Use ClimKern's \"hidden\" make_tropo function\n", + "pert_cesm2[\"trop_p\"] = ck.util.make_tropo(pert_cesm2.ps)\n", + "pert_cesm2.trop_p.attrs[\"units\"] = \"Pa\"\n", + "\n", + "lr_cesm2,pl_cesm2 = ck.calc_T_feedbacks(\n", + " ctrl_cesm2.ta,ctrl_cesm2.ts,ctrl_cesm2.ps,\n", + " pert_cesm2.ta,pert_cesm2.ts,pert_cesm2.ps,\n", + " pert_trop=pert_cesm2.trop_p,\n", + " kern=\"ERA5\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pert_cm4[\"trop_p\"] = ck.util.make_tropo(pert_cm4.ps)\n", + "pert_cm4.trop_p.attrs[\"units\"] = \"Pa\"\n", + "\n", + "lr_cm4,pl_cm4 = ck.calc_T_feedbacks(\n", + " ctrl_cm4.ta, ctrl_cm4.ts, ctrl_cm4.ps,\n", + " pert_cm4.ta, pert_cm4.ts, pert_cm4.ps,\n", + " pert_trop=pert_cm4.trop_p,\n", + " kern=\"ERA5\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lr_cesm2_norm_mean = (lr_cesm2.mean(dim='time') / Δts_cesm2.mean(dim=\"time\")).squeeze()\n", + "lr_cm4_norm_mean = (lr_cm4.mean(dim='time') / Δts_cm4.mean(dim=\"time\")).squeeze()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "proj = ccrs.Robinson()\n", + "\n", + "fig, axes = plt.subplots(1,2, figsize=(10,4), subplot_kw=dict(projection=proj),\n", + " layout=\"constrained\")\n", + "\n", + "plotargs = {\"vmin\": -7,\n", + " \"vmax\": 7,\n", + " \"cmap\": \"RdBu_r\",\n", + " \"cbar_kwargs\": {\"orientation\": \"horizontal\", \"shrink\": 0.7,\n", + " \"label\":\"W/m$^2$/K\"}\n", + " }\n", + "\n", + "ax = axes[0]\n", + "(lr_cesm2_norm_mean).plot(ax=ax,transform=ccrs.PlateCarree(), **plotargs)\n", + "ax.set_title('CESM2')\n", + "\n", + "ax = axes[1]\n", + "(lr_cm4_norm_mean).plot(ax=ax,transform=ccrs.PlateCarree(), **plotargs)\n", + "ax.set_title('GFDL CM4')\n", + "\n", + "for ax in axes:\n", + " ax.coastlines()\n", + " \n", + "fig.suptitle('Lapse Rate Feedback (multi-model comparison)');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Water vapor feedbacks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Use ClimKern's water vapor feedback function\n", + "qlw_cesm2, qsw_cesm2 = ck.calc_q_feedbacks(\n", + " ctrl_cesm2.hus, ctrl_cesm2.ta, ctrl_cesm2.ps,\n", + " pert_cesm2.hus, pert_cesm2.ps, pert_trop=pert_cesm2.trop_p,\n", + " kern=\"ERA5\",\n", + " method=1\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qlw_cm4, qsw_cm4 = ck.calc_q_feedbacks(\n", + " ctrl_cm4.hus, ctrl_cm4.ta, ctrl_cm4.ps,\n", + " pert_cm4.hus, pert_cm4.ps, pert_trop=pert_cm4.trop_p,\n", + " kern=\"ERA5\",\n", + " method=1\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qlw_cesm2_norm_mean = (qlw_cesm2.mean(dim='time') / Δts_cesm2.mean(dim=\"time\")).squeeze()\n", + "qlw_cm4_norm_mean = (qlw_cm4.mean(dim='time') / Δts_cm4.mean(dim=\"time\")).squeeze()\n", + "\n", + "qsw_cesm2_norm_mean = (qsw_cesm2.mean(dim='time') / Δts_cesm2.mean(dim=\"time\")).squeeze()\n", + "qsw_cm4_norm_mean = (qsw_cm4.mean(dim='time') / Δts_cm4.mean(dim=\"time\")).squeeze()\n", + "\n", + "qtotal_cesm2_norm_mean = qlw_cesm2_norm_mean + qsw_cesm2_norm_mean\n", + "qtotal_cm4_norm_mean = qlw_cm4_norm_mean + qsw_cm4_norm_mean" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's take a look!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(1,2, figsize=(10,4), subplot_kw=dict(projection=proj),\n", + " layout=\"constrained\")\n", + "\n", + "plotargs = {\"vmin\": 0,\n", + " \"vmax\": 10,\n", + " \"cmap\": \"Reds\",\n", + " \"cbar_kwargs\": {\"orientation\": \"horizontal\", \"shrink\": 0.7,\n", + " \"label\":\"W/m$^2$/K\"}\n", + " }\n", + "\n", + "ax = axes[0]\n", + "(qtotal_cesm2_norm_mean).plot(ax=ax,transform=ccrs.PlateCarree(), **plotargs)\n", + "ax.set_title('CESM2')\n", + "\n", + "ax = axes[1]\n", + "(qtotal_cm4_norm_mean).plot(ax=ax,transform=ccrs.PlateCarree(), **plotargs)\n", + "ax.set_title('GFDL CM4')\n", + "\n", + "for ax in axes:\n", + " ax.coastlines()\n", + " \n", + "fig.suptitle('Water Vapor Feedback (multi-model comparison)');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that GFDL CM4 tends to have larger magnitudes of *both* the lapse rate and water vapor feedbacks compared to CESM2." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Combined water vapor plus lapse rate feedback" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combo_cesm2 = qtotal_cesm2_norm_mean + lr_cesm2_norm_mean\n", + "combo_cm4 = qtotal_cm4_norm_mean + lr_cm4_norm_mean" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(1,2, figsize=(10,4), subplot_kw=dict(projection=proj),\n", + " layout=\"constrained\")\n", + "\n", + "plotargs = {\"vmin\": -7,\n", + " \"vmax\": 7,\n", + " \"cmap\": \"RdBu_r\",\n", + " \"cbar_kwargs\": {\"orientation\": \"horizontal\", \"shrink\": 0.7,\n", + " \"label\":\"W/m$^2$/K\"}\n", + " }\n", + "\n", + "ax = axes[0]\n", + "(combo_cesm2).plot(ax=ax,transform=ccrs.PlateCarree(), **plotargs)\n", + "ax.set_title('CESM2')\n", + "\n", + "ax = axes[1]\n", + "(combo_cm4).plot(ax=ax,transform=ccrs.PlateCarree(), **plotargs)\n", + "ax.set_title('GFDL CM4')\n", + "\n", + "for ax in axes:\n", + " ax.coastlines()\n", + " \n", + "fig.suptitle('Combined water vapor plus lapse rate feedback (multi-model comparison)');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looks like the differences between the two models are more modest when we combine the two feedbacks!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tabulate the global mean results\n", + "\n", + "We'll display the global mean values in units of W m$^{-^2}$ K$^{-1}$:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def global_mean(data):\n", + " weights = np.cos(np.deg2rad(data.lat))\n", + " return data.weighted(weights=weights).mean(dim=['lat','lon']).compute()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "results = pd.DataFrame([\n", + " [float(global_mean(qtotal_cesm2_norm_mean)), float(global_mean(lr_cesm2_norm_mean)), float(global_mean(combo_cesm2))], \n", + " [float(global_mean(qtotal_cm4_norm_mean)), float(global_mean(lr_cm4_norm_mean)), float(global_mean(combo_cm4))],\n", + " ], \n", + " columns=[\"Water vapor \", \"Lapse rate\", \"Combined WV+LR\"],\n", + " index=[\"CESM2\", \"GFDL CM4\"],\n", + " )\n", + "results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, in conclusion, yes the difference in the combined feedbacks is smaller than the differences in the individual feedbacks!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## To do\n", + "\n", + "Some interesting additions to this notebook:\n", + "\n", + "- Refactor the code to loop through models rather than copy and paste the repeated operations\n", + "- Bring some more models into the comparison\n", + "- Express feedbacks in the alternate relative humidity framework discussed by {cite:t}`Held:2012a`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + }, + "nbdime-conflicts": { + "local_diff": [ + { + "diff": [ + { + "diff": [ + { + "key": 0, + "op": "addrange", + "valuelist": [ + "Python 3" + ] + }, + { + "key": 0, + "length": 1, + "op": "removerange" + } + ], + "key": "display_name", + "op": "patch" + } + ], + "key": "kernelspec", + "op": "patch" + } + ], + "remote_diff": [ + { + "diff": [ + { + "diff": [ + { + "key": 0, + "op": "addrange", + "valuelist": [ + "Python3" + ] + }, + { + "key": 0, + "length": 1, + "op": "removerange" + } + ], + "key": "display_name", + "op": "patch" + } + ], + "key": "kernelspec", + "op": "patch" + } + ] + }, + "toc-autonumbering": false + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 4918d8f7cca40dd1a219f99f1540b9a4749ae92c Mon Sep 17 00:00:00 2001 From: brian-rose Date: Fri, 8 Aug 2025 19:14:50 +0000 Subject: [PATCH 2/3] Conditional kernel data access --- notebooks/foundations/multi-model.ipynb | 91 ++++++++++--------------- 1 file changed, 35 insertions(+), 56 deletions(-) diff --git a/notebooks/foundations/multi-model.ipynb b/notebooks/foundations/multi-model.ipynb index 587f615e..37dcf2ff 100644 --- a/notebooks/foundations/multi-model.ipynb +++ b/notebooks/foundations/multi-model.ipynb @@ -89,22 +89,11 @@ ":::" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "URL = \"https://js2.jetstream-cloud.org:8001/\" # URL for jetstream access\n", - "path = f\"pythia/ClimKern\" # Location of ClimKern\n", - "kernel = \"ERA5\"" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Next, read in the data from Jetstream2" + "Here we are (temporarily) making the kernel data access conditional on whether the files are already present in the local workspace:" ] }, { @@ -113,51 +102,41 @@ "metadata": {}, "outputs": [], "source": [ - "# Read in data\n", - "# Set up access to jetstream2\n", - "fs = fsspec.filesystem(\"s3\", anon=True, client_kwargs=dict(endpoint_url=URL))\n", - "pattern = f\"s3://{path}/kernels/\"+kernel+f\"/TOA*.nc\"\n", - "\n", - "# Grab the data\n", - "files = sorted(fs.glob(pattern))\n", - "fs.invalidate_cache() # This is necessary to deal with peculiarities of objects served from jetstream2\n", - "\n", - "# Open file and make it Xarray Dataset\n", - "kern = xr.open_dataset(fs.open(files[0]))\n", - "\n", - "# Save path for later\n", - "path_out = files[0].split(kernel+\"/\",1)[1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To save this data in ClimKern's directory, we have to figure out where it is on the\n", - "machine. After that, we will go ahead and save out the kernel as a netCDF in \n", - "ClimKern's local data directory." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Get the package location\n", - "spec = importlib.util.find_spec(\"climkern\")\n", - "package_dir = os.path.dirname(spec.origin)\n", - "\n", - "# print(f\"The package directory is: {package_dir}\")\n", - "\n", - "# Define the path where you want to save the netCDF file within the package directory\n", - "netcdf_path = os.path.join(package_dir,\"data/kernels\",kernel,path_out)\n", - "\n", - "# Ensure the directory exists\n", - "os.makedirs(os.path.dirname(netcdf_path), exist_ok=True)\n", + "try:\n", + " ck.util.get_kern(kernel)\n", + " print(\"The kernels are already accessible.\")\n", + "except:\n", + " print(\"We need to download the kernel data.\")\n", + " URL = \"https://js2.jetstream-cloud.org:8001/\" # URL for jetstream access\n", + " path = f\"pythia/ClimKern\" # Location of ClimKern\n", + " kernel = \"ERA5\"\n", + " fs = fsspec.filesystem(\"s3\", anon=True, client_kwargs=dict(endpoint_url=URL))\n", + " pattern = f\"s3://{path}/kernels/\"+kernel+f\"/TOA*.nc\"\n", + " \n", + " # Grab the data\n", + " files = sorted(fs.glob(pattern))\n", + " fs.invalidate_cache() # This is necessary to deal with peculiarities of objects served from jetstream2\n", + " \n", + " # Open file and make it Xarray Dataset\n", + " kern = xr.open_dataset(fs.open(files[0]))\n", + " \n", + " # Save path for later\n", + " path_out = files[0].split(kernel+\"/\",1)[1]\n", "\n", - "# Save the dataset as a netCDF file\n", - "kern.to_netcdf(netcdf_path)" + " # Get the package location\n", + " spec = importlib.util.find_spec(\"climkern\")\n", + " package_dir = os.path.dirname(spec.origin)\n", + " \n", + " # print(f\"The package directory is: {package_dir}\")\n", + " \n", + " # Define the path where you want to save the netCDF file within the package directory\n", + " netcdf_path = os.path.join(package_dir,\"data/kernels\",kernel,path_out)\n", + " \n", + " # Ensure the directory exists\n", + " os.makedirs(os.path.dirname(netcdf_path), exist_ok=True)\n", + " \n", + " # Save the dataset as a netCDF file\n", + " kern.to_netcdf(netcdf_path)" ] }, { From a244a2d068b655a9ef6386e27eb6332d3d222f46 Mon Sep 17 00:00:00 2001 From: brian-rose Date: Fri, 8 Aug 2025 19:31:21 +0000 Subject: [PATCH 3/3] fix an oops --- notebooks/foundations/multi-model.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/notebooks/foundations/multi-model.ipynb b/notebooks/foundations/multi-model.ipynb index 37dcf2ff..cb976ef6 100644 --- a/notebooks/foundations/multi-model.ipynb +++ b/notebooks/foundations/multi-model.ipynb @@ -102,6 +102,8 @@ "metadata": {}, "outputs": [], "source": [ + "kernel = \"ERA5\"\n", + "\n", "try:\n", " ck.util.get_kern(kernel)\n", " print(\"The kernels are already accessible.\")\n", @@ -109,7 +111,6 @@ " print(\"We need to download the kernel data.\")\n", " URL = \"https://js2.jetstream-cloud.org:8001/\" # URL for jetstream access\n", " path = f\"pythia/ClimKern\" # Location of ClimKern\n", - " kernel = \"ERA5\"\n", " fs = fsspec.filesystem(\"s3\", anon=True, client_kwargs=dict(endpoint_url=URL))\n", " pattern = f\"s3://{path}/kernels/\"+kernel+f\"/TOA*.nc\"\n", " \n",