diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..b06e6e1 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,4 @@ +[codespell] + +# Ignore really long strings that look like base64-encoded data, e.g. in Jupyter Notebook outputs +ignore-regex = [A-Za-z0-9+/]{100,} diff --git a/02-schedule.md b/02-schedule.md index 61932a4..2ba73b0 100644 --- a/02-schedule.md +++ b/02-schedule.md @@ -36,8 +36,8 @@ Vegetarian options will be available and all ingredients will be listed. ## šŸŒ‡ Afternoon (1:30 - 5:00) | Time | Duration | Topic | Presenter(s) | -| ------- | ---------- | ---------------------------------------------- | --------------- | -| 1:30 PM | 60 minutes | [](modules/04-data-in-the-cloud/index.md) | Max | +| ------- | ---------- | -----------------------------------------------| --------------- | +| 1:30 PM | 60 minutes | [](modules/04-data-in-the-cloud/index.ipynb) | Max | | 2:30 PM | 10 minutes | **Break** | | | 2:40 PM | 60 minutes | [](modules/05-sharing-and-publishing/index.md) | Fernando & Matt | | 3:40 PM | 10 minutes | **Break** | | diff --git a/docker/environment.yml b/docker/environment.yml index 00bf5ee..ac1ef97 100644 --- a/docker/environment.yml +++ b/docker/environment.yml @@ -22,6 +22,14 @@ dependencies: - "scipy" - "xarray" + # Storage + - "s3fs" + - "obstore" + - "fsspec" + - "icechunk" + - "virtualizarr" + - "h5netcdf" + # geo - "gdal" - "geopandas" @@ -51,9 +59,17 @@ dependencies: - "ruff" - "pre-commit" + # Dependency management + - "uv" + - "pixi" + # GitHub utilities - "gh" - "gh-scoped-creds" # Infrastructure - "jupyter-server-proxy" # Enable proxying MyST sites built from CLI, accessed at `$PREFIX/proxy/$PORT` + + - "pip" + - pip: # Stuff not yet on conda forge + - "obspec-utils" diff --git a/modules/04-data-in-the-cloud/aws_regions.jpg b/modules/04-data-in-the-cloud/aws_regions.jpg new file mode 100644 index 0000000..77822e7 Binary files /dev/null and b/modules/04-data-in-the-cloud/aws_regions.jpg differ diff --git a/modules/04-data-in-the-cloud/cloud_data_timings.json b/modules/04-data-in-the-cloud/cloud_data_timings.json new file mode 100644 index 0000000..59177bc --- /dev/null +++ b/modules/04-data-in-the-cloud/cloud_data_timings.json @@ -0,0 +1,62 @@ +{ + "cloud": { + "description": "JupyterHub - us-west-2", + "timestamp": "2025-12-11 16:08:23", + "timings": { + "open": { + "fsspec_default_cache": 467.4452876969999, + "fsspec_block_cache": 65.81539951800005, + "obstore": 127.15990847400008, + "virtualzarr_icechunk": 2.579058487000111 + }, + "spatial_subset_load": { + "fsspec_default_cache": 0.6042853349999859, + "fsspec_block_cache": 0.025397076999979618, + "obstore": 0.2673111340000105, + "virtualzarr_icechunk": 2.010724728000241 + }, + "time_slice_load": { + "fsspec_default_cache": 30.614898986999833, + "fsspec_block_cache": 2.269339702999787, + "obstore": 7.793771065999863, + "virtualzarr_icechunk": 2.974931951999679 + }, + "timeseries_load": { + "fsspec_default_cache": 0.2537525479992837, + "fsspec_block_cache": 4.6087899290005225, + "obstore": 8.809160454999983, + "virtualzarr_icechunk": 1.723680136999974 + } + } + }, + "local": { + "description": "MacBook Pro - Durham, NC", + "timestamp": "2025-12-11 12:18:33", + "timings": { + "open": { + "fsspec_default_cache": 1819.1012450830003, + "fsspec_block_cache": 278.71061658300096, + "obstore": 434.0572458329989, + "virtualzarr_icechunk": 2.463514082999609 + }, + "spatial_subset_load": { + "fsspec_default_cache": 0.6070321250008419, + "fsspec_block_cache": 0.052599582999391714, + "obstore": 0.7011319169996568, + "virtualzarr_icechunk": 2.534569625000586 + }, + "time_slice_load": { + "fsspec_default_cache": 136.6284629999991, + "fsspec_block_cache": 4.599118041998736, + "obstore": 26.773726958001134, + "virtualzarr_icechunk": 5.418655583000145 + }, + "timeseries_load": { + "fsspec_default_cache": 0.1992982080009824, + "fsspec_block_cache": 19.891594749999058, + "obstore": 18.95907412500128, + "virtualzarr_icechunk": 1.2114160420005646 + } + } + } +} diff --git a/modules/04-data-in-the-cloud/index.ipynb b/modules/04-data-in-the-cloud/index.ipynb new file mode 100644 index 0000000..4eebb4d --- /dev/null +++ b/modules/04-data-in-the-cloud/index.ipynb @@ -0,0 +1,3024 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "54832c99", + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "source": [ + "---\n", + "authors:\n", + " - name: \"Max Jones\"\n", + " affiliations:\n", + " - \"Development Seed\"\n", + " email: \"max@developmentseed.org\"\n", + " orcid: \"0000-0003-0180-8928\"\n", + " github: \"maxrjones\"\n", + "---\n", + "\n", + "# ā˜ļø 4 - Data in the Cloud 101\n", + "\n", + ":::{tip} 🧭 Where we are going\n", + ":icon: false\n", + "\n", + "By the end of this module, you will be able to:\n", + "\n", + "- Explain the key benefits and pitfalls of working with data on the Cloud\n", + "- Open cloud hosted data in a performant way\n", + "- Scale your analyses using Dask or Cubed\n", + "- Find communities to learn more about cloud native science\n", + "- **Compare performance between local and cloud-based computing**\n", + ":::\n", + "\n", + "\n", + "## Introduction\n", + "\n", + "This notebook is for the workshop ([Open Source Geospatial Workflows in the Cloud](https://geojupyter.github.io/workshop-open-source-geospatial)) presented at the [AGU Fall Meeting 2025](https://agu.confex.com/agu/agu25/meetingapp.cgi/Session/252640).\n", + "\n", + "### šŸ†• Local vs. Cloud Comparison Feature\n", + "\n", + "This notebook supports running in both **local** and **cloud** environments to demonstrate the performance benefits of \"data-proximate computing\". Run it once locally, then again on cloud infrastructure (e.g., AWS us-west-2) to see the dramatic difference in performance when your compute is co-located with your data." + ] + }, + { + "cell_type": "markdown", + "id": "89da0fe3", + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "source": [ + "## What is the cloud?\n", + "\n", + "The cloud (as defined by [cloudflare](https://www.cloudflare.com/)) is a distributed collection of servers that host software and infrastructure, and it is accessed over the Internet. The map below (from [salesforce](https://trailhead.salesforce.com/content/learn/modules/aws-cloud-technical-professionals/explore-the-aws-global-infrastructure-technical-professionals)) shows [Amazon Web Service (AWS)](https://aws.amazon.com)'s global distribution of data centers, which contain the resources that make up the AWS cloud. Three large cloud providers in the United States are [AWS](https://aws.amazon.com/), [Google Cloud Platform](https://cloud.google.com/), and [Microsoft Azure](https://azure.microsoft.com/en-us), but other cloud providers are larger elsewhere in the world and there are numerous smaller providers available.\n", + "\n", + "![AWS regions](./aws_regions.jpg)" + ] + }, + { + "cell_type": "markdown", + "id": "ad673655", + "metadata": {}, + "source": [ + "## What makes data on the cloud different?\n", + "\n", + "Hosting data on the cloud differs from storing data locally (or on-premises) in a few important ways:\n", + "\n", + "- Redundancy - you can easily replicate your data across multiple servers, which may be distributed across the globe\n", + "- Reliability - cloud providers offer services for reliability, such as automated backups and recovery\n", + "- Scalability - cloud object storage enables nearly limitless simultaneous access across users/connections, without needing to order or decommission servers or hard-drives\n", + "- Accessibility - anyone in the world, with proper authorization, can rapidly access data shared on the cloud\n", + "\n", + "Gotchas: There are a couple of considerations to be aware of when working with data on the cloud:\n", + "\n", + "- Pay-as-you-go - Most cloud providers use pay-as-you-go pricing, where you only pay for the storage and services that you use. This can potentially reduce costs, especially upfront costs (e.g., you never need to buy a hard drive). However, **you may want to provide indefinite access or you may forget about data in storage, in both cases you may end up continuing to pay for data storage indefinitely**.\n", + "- Time and cost of bringing data to your computer - Hosting the data on the cloud naturally means it's no longer already near your computer's processing resources. Transporting data from the cloud to your computer is expensive, since most cloud providers charge for any data leaving their network, and slow, since the data needs to travel large distances. The primary solution for this is \"data-proximate computing\" which involves running your code on computing resources in the same cloud location as your data. For example, I commonly use NASA data products that are hosted on AWS servers in the 'us-west-2' region, which corresponds to Oregon in the figure above. Following the \"data-proximate computing\" paradigm, I use AWS compute resources that are also in Oregon when working with those data, rather than downloading data to use the computing resources on my laptop in North Carolina. In addition to \"data-proximate computing\", there are many other ways to make working with data on the cloud cheaper and easier. Let's take a look!" + ] + }, + { + "cell_type": "markdown", + "id": "286f73e5", + "metadata": {}, + "source": [ + "## What is cloud-native data?\n", + "\n", + "Cloud-native data are structured for efficient querying across a network. For this 101 tutorial, you can think of \"a network\" as synonymous with \"the internet\". You can learn more about these data in the [CNG data formats guide](https://guide.cloudnativegeo.org/), but here we'll just explore working with data that is, compared to data that isn't, optimized for cloud usage." + ] + }, + { + "cell_type": "markdown", + "id": "0e6efe60", + "metadata": {}, + "source": [ + "### Setup and Helper Functions\n", + "\n", + "First import the necessary libraries:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0b4d349c", + "metadata": {}, + "outputs": [], + "source": [ + "import fsspec\n", + "import numpy as np\n", + "import xarray as xr\n", + "from time import perf_counter\n", + "import time # for timestamps only\n", + "import warnings\n", + "import json\n", + "import os\n", + "from pathlib import Path\n", + "\n", + "warnings.filterwarnings(\n", + " \"ignore\",\n", + " message=\"Numcodecs codecs are not in the Zarr version 3 specification*\",\n", + " category=UserWarning\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "34f650d4", + "metadata": {}, + "source": [ + "### šŸ†• Environment Configuration\n", + "\n", + "Configure whether you're running locally or on cloud infrastructure. This notebook will save timings to a JSON file so you can compare results from different environments." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "84534bcd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "šŸ–„ļø Running in 'LOCAL' environment\n", + "šŸ“ Description: MacBook Pro - Durham, NC\n" + ] + } + ], + "source": [ + "# =============================================================================\n", + "# ENVIRONMENT CONFIGURATION - MODIFY THIS FOR YOUR RUN\n", + "# =============================================================================\n", + "\n", + "# Set to \"local\" when running on your local machine (not in the cloud)\n", + "# Set to \"cloud\" when running on cloud infrastructure (e.g., AWS us-west-2)\n", + "ENVIRONMENT = \"local\" # Options: \"local\" or \"cloud\"\n", + "\n", + "# Optional: Add a description for this run (e.g., your location, machine specs)\n", + "RUN_DESCRIPTION = \"MacBook Pro - Durham, NC\"\n", + "\n", + "# File to store timing results for comparison\n", + "TIMINGS_FILE = Path(\"cloud_data_timings.json\")\n", + "\n", + "print(f\"šŸ–„ļø Running in '{ENVIRONMENT.upper()}' environment\")\n", + "print(f\"šŸ“ Description: {RUN_DESCRIPTION}\")" + ] + }, + { + "cell_type": "markdown", + "id": "a7b5e1c1", + "metadata": {}, + "source": [ + "Set up timing dictionary and helper functions:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "44da9087", + "metadata": {}, + "outputs": [], + "source": [ + "# Dictionary to store timings for current run\n", + "timings = {\n", + " 'open': {},\n", + " 'spatial_subset_load': {},\n", + " 'time_slice_load': {},\n", + " 'timeseries_load': {},\n", + "}\n", + "\n", + "# Constants for consistent test parameters\n", + "SPATIAL_SUBSET_KWARGS = {\"time\": \"2001-01-02\", \"lat\": slice(10, 15), \"lon\": slice(-60, -55)}\n", + "TIME_SLICE_KWARGS = {\"time\": \"2001-01-10\"}\n", + "SPATIAL_POINT_KWARGS = {\"lat\": 45, \"lon\": -150, \"method\": \"nearest\"}\n", + "N_FILES = 30\n", + "\n", + "def record_timing(category, method, elapsed_time):\n", + " \"\"\"Helper to record timing results (using perf_counter for precision).\"\"\"\n", + " timings[category][method] = elapsed_time\n", + " print(f\" ā±ļø {category} / {method}: {elapsed_time:.2f}s\")\n", + "\n", + "\n", + "def benchmark_method(ds, method_name, n_files=N_FILES):\n", + " \"\"\"Run all benchmark tests on a dataset and record timings.\"\"\"\n", + " \n", + " start = perf_counter()\n", + " data = ds['Tair'].sel(**SPATIAL_SUBSET_KWARGS).load()\n", + " record_timing('spatial_subset_load', method_name, perf_counter() - start)\n", + "\n", + " start = perf_counter()\n", + " data = ds['Tair'].sel(**TIME_SLICE_KWARGS).load()\n", + " record_timing('time_slice_load', method_name, perf_counter() - start)\n", + "\n", + " start = perf_counter()\n", + " data = ds['Tair'].sel(**SPATIAL_POINT_KWARGS).isel(time=slice(0, n_files)).load()\n", + " record_timing('timeseries_load', method_name, perf_counter() - start)\n", + " \n", + " return data\n", + "\n", + "\n", + "def print_summary(method_name):\n", + " \"\"\"Print a summary of timings for a method.\"\"\"\n", + " total = sum(timings[op].get(method_name, 0) for op in timings.keys())\n", + " print(f\"\\n ā”Œ{'─'*50}\")\n", + " print(f\" │ šŸ“‹ Summary for {method_name}\")\n", + " print(f\" ā”œ{'─'*50}\")\n", + " print(f\" │ Open: {timings['open'].get(method_name, 0):>8.2f}s\")\n", + " print(f\" │ Spatial subset: {timings['spatial_subset_load'].get(method_name, 0):>8.2f}s\")\n", + " print(f\" │ Time slice: {timings['time_slice_load'].get(method_name, 0):>8.2f}s\")\n", + " print(f\" │ Time series: {timings['timeseries_load'].get(method_name, 0):>8.2f}s\")\n", + " print(f\" ā”œ{'─'*50}\")\n", + " print(f\" │ TOTAL: {total:>8.2f}s\")\n", + " print(f\" ā””{'─'*50}\\n\")\n", + "\n", + "\n", + "def load_all_timings():\n", + " \"\"\"Load all saved timing results from file.\"\"\"\n", + " if TIMINGS_FILE.exists():\n", + " with open(TIMINGS_FILE, 'r') as f:\n", + " return json.load(f)\n", + " return {}\n", + "\n", + "\n", + "def save_current_timings():\n", + " \"\"\"Save current timing results to file.\"\"\"\n", + " all_timings = load_all_timings()\n", + " \n", + " # Create entry for this run\n", + " run_entry = {\n", + " 'description': RUN_DESCRIPTION,\n", + " 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),\n", + " 'timings': timings\n", + " }\n", + " \n", + " # Store under environment key\n", + " all_timings[ENVIRONMENT] = run_entry\n", + " \n", + " with open(TIMINGS_FILE, 'w') as f:\n", + " json.dump(all_timings, f, indent=2)\n", + " \n", + " print(f\"\\nāœ… Timings saved to {TIMINGS_FILE}\")\n", + " print(f\" Environment: {ENVIRONMENT}\")\n", + " print(f\" Timestamp: {run_entry['timestamp']}\")\n", + "\n", + "\n", + "def get_saved_environments():\n", + " \"\"\"Get list of environments with saved timings.\"\"\"\n", + " all_timings = load_all_timings()\n", + " return list(all_timings.keys())" + ] + }, + { + "cell_type": "markdown", + "id": "09d4e400", + "metadata": {}, + "source": [ + "List the files available following this pattern on AWS S3 storage:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a51c6f89", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'s3://nasa-waterinsight/NLDAS3/forcing/daily/200101/NLDAS_FOR0010_D.A20010101.030.beta.nc'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fs = fsspec.filesystem('s3', anon=True)\n", + "nldas_files = fs.glob('s3://nasa-waterinsight/NLDAS3/forcing/daily/**/*.nc')\n", + "nldas_files = sorted(['s3://'+f for f in nldas_files])\n", + "nldas_files[0]" + ] + }, + { + "cell_type": "markdown", + "id": "252e7449", + "metadata": {}, + "source": [ + "### Opening archival data with fsspec + h5netcdf" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0c84f267", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "šŸ“Š Testing: fsspec + h5netcdf (default cache)\n", + " ā±ļø open / fsspec_default_cache: 1819.10s\n", + " ā±ļø spatial_subset_load / fsspec_default_cache: 0.61s\n", + " ā±ļø time_slice_load / fsspec_default_cache: 136.63s\n", + " ā±ļø timeseries_load / fsspec_default_cache: 0.20s\n", + "\n", + " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€\n", + " │ šŸ“‹ Summary for fsspec_default_cache\n", + " ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€\n", + " │ Open: 1819.10s\n", + " │ Spatial subset: 0.61s\n", + " │ Time slice: 136.63s\n", + " │ Time series: 0.20s\n", + " ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€\n", + " │ TOTAL: 1956.54s\n", + " └──────────────────────────────────────────────────\n", + "\n" + ] + } + ], + "source": [ + "print(f\"\\nšŸ“Š Testing: fsspec + h5netcdf (default cache)\")\n", + "start = perf_counter()\n", + "fs = fsspec.filesystem('s3', anon=True)\n", + "file_objs = [fs.open(f) for f in nldas_files[:N_FILES]]\n", + "ds = xr.open_mfdataset(file_objs, engine=\"h5netcdf\", combine=\"nested\", concat_dim=\"time\")\n", + "record_timing('open', 'fsspec_default_cache', perf_counter() - start)\n", + "\n", + "benchmark_method(ds, 'fsspec_default_cache')\n", + "print_summary('fsspec_default_cache')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "6cc41725", + "metadata": {}, + "outputs": [], + "source": [ + "[f.close() for f in file_objs]\n", + "fs.clear_instance_cache()\n", + "del fs, file_objs, ds" + ] + }, + { + "cell_type": "markdown", + "id": "ad2c97f0", + "metadata": {}, + "source": [ + "This took a lot of time to open the file. Let's look how we can speed that up by configuring the caching strategy:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bd8645bc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "šŸ“Š Testing: fsspec + h5netcdf (block cache)\n", + " ā±ļø open / fsspec_block_cache: 278.71s\n", + " ā±ļø spatial_subset_load / fsspec_block_cache: 0.05s\n", + " ā±ļø time_slice_load / fsspec_block_cache: 4.60s\n", + " ā±ļø timeseries_load / fsspec_block_cache: 19.89s\n", + "\n", + " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€\n", + " │ šŸ“‹ Summary for fsspec_block_cache\n", + " ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€\n", + " │ Open: 278.71s\n", + " │ Spatial subset: 0.05s\n", + " │ Time slice: 4.60s\n", + " │ Time series: 19.89s\n", + " ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€\n", + " │ TOTAL: 303.25s\n", + " └──────────────────────────────────────────────────\n", + "\n" + ] + } + ], + "source": [ + "print(f\"\\nšŸ“Š Testing: fsspec + h5netcdf (block cache)\")\n", + "start = perf_counter()\n", + "fsspec_caching = {\n", + " \"cache_type\": \"blockcache\",\n", + " \"block_size\": 1024 * 1024 * 8,\n", + "}\n", + "fs = fsspec.filesystem('s3', anon=True)\n", + "file_objs = [fs.open(f, **fsspec_caching) for f in nldas_files[:N_FILES]]\n", + "ds = xr.open_mfdataset(file_objs, engine=\"h5netcdf\", combine=\"nested\", concat_dim=\"time\")\n", + "record_timing('open', 'fsspec_block_cache', perf_counter() - start)\n", + "\n", + "benchmark_method(ds, 'fsspec_block_cache')\n", + "print_summary('fsspec_block_cache')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cf5239ab", + "metadata": {}, + "outputs": [], + "source": [ + "[f.close() for f in file_objs]\n", + "fs.clear_instance_cache()\n", + "del fs, file_objs, ds" + ] + }, + { + "cell_type": "markdown", + "id": "57a8ae8f", + "metadata": {}, + "source": [ + "### Opening archival data using VirtualiZarr + Icechunk\n", + "\n", + "Now, for the really cool part! Using [VirtualiZarr](https://virtualizarr.readthedocs.io/) + [Icechunk](https://icechunk.io/), we can rapidly open not just that file but all of the files included in the NLDAS3 dataset! In less than 2 seconds, we can have a lazy view of a dataset that contains 24 years of data. People will often use the term \"lazy loading\" when an operation loads metadata from a storage location, but does not load any actual data. Without the cloud-native adaptation virtual Zarr, it's not possible for a software library to determine how much data it should load from disk to get all the necessary metadata. Virtual Zarr is a faster, cheaper, and easier way to work with data on the cloud :rocket:." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7a0ca8fc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import icechunk\n", + "import zarr\n", + "import xarray as xr\n", + "\n", + "zarr.config.set({'threading.max_workers': 32, 'async.concurrency': 128})" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cf0d2e56", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "šŸ“Š Testing: VirtualiZarr + Icechunk\n", + " ā±ļø open / virtualzarr_icechunk: 2.46s\n", + " ā±ļø spatial_subset_load / virtualzarr_icechunk: 2.53s\n", + " ā±ļø time_slice_load / virtualzarr_icechunk: 5.42s\n", + " ā±ļø timeseries_load / virtualzarr_icechunk: 1.21s\n", + "\n", + " ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€\n", + " │ šŸ“‹ Summary for virtualzarr_icechunk\n", + " ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€\n", + " │ Open: 2.46s\n", + " │ Spatial subset: 2.53s\n", + " │ Time slice: 5.42s\n", + " │ Time series: 1.21s\n", + " ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€\n", + " │ TOTAL: 11.63s\n", + " └──────────────────────────────────────────────────\n", + "\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 51TB\n",
+       "Dimensions:   (time: 8399, lat: 6500, lon: 11700)\n",
+       "Coordinates:\n",
+       "  * time      (time) datetime64[ns] 67kB 2001-01-02 2001-01-03 ... 2024-01-01\n",
+       "  * lat       (lat) float64 52kB 7.005 7.015 7.025 7.035 ... 71.97 71.98 71.99\n",
+       "  * lon       (lon) float64 94kB -169.0 -169.0 -169.0 ... -52.03 -52.01 -52.0\n",
+       "Data variables:\n",
+       "    LWdown    (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "    PSurf     (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "    Rainf     (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "    Wind_N    (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "    Qair      (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "    Tair      (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "    Wind_E    (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "    SWdown    (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "    Tair_max  (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "    Tair_min  (time, lat, lon) float64 5TB dask.array<chunksize=(1, 500, 900), meta=np.ndarray>\n",
+       "Attributes: (12/17)\n",
+       "    missing_value:          -9999.0\n",
+       "    time_definition:        daily\n",
+       "    shortname:              NLDAS_FOR0010_D_3.0\n",
+       "    title:                  NLDAS Forcing Data L4 Daily 0.01 x 0.01 degree V3...\n",
+       "    version:                3.0 beta\n",
+       "    institution:            NASA GSFC\n",
+       "    ...                     ...\n",
+       "    websites:               https://ldas.gsfc.nasa.gov/nldas/v3/ ; https://li...\n",
+       "    MAP_PROJECTION:         EQUIDISTANT CYLINDRICAL\n",
+       "    SOUTH_WEST_CORNER_LAT:  7.005000114440918\n",
+       "    SOUTH_WEST_CORNER_LON:  -168.9949951171875\n",
+       "    DX:                     0.009999999776482582\n",
+       "    DY:                     0.009999999776482582
" + ], + "text/plain": [ + " Size: 51TB\n", + "Dimensions: (time: 8399, lat: 6500, lon: 11700)\n", + "Coordinates:\n", + " * time (time) datetime64[ns] 67kB 2001-01-02 2001-01-03 ... 2024-01-01\n", + " * lat (lat) float64 52kB 7.005 7.015 7.025 7.035 ... 71.97 71.98 71.99\n", + " * lon (lon) float64 94kB -169.0 -169.0 -169.0 ... -52.03 -52.01 -52.0\n", + "Data variables:\n", + " LWdown (time, lat, lon) float64 5TB dask.array\n", + " PSurf (time, lat, lon) float64 5TB dask.array\n", + " Rainf (time, lat, lon) float64 5TB dask.array\n", + " Wind_N (time, lat, lon) float64 5TB dask.array\n", + " Qair (time, lat, lon) float64 5TB dask.array\n", + " Tair (time, lat, lon) float64 5TB dask.array\n", + " Wind_E (time, lat, lon) float64 5TB dask.array\n", + " SWdown (time, lat, lon) float64 5TB dask.array\n", + " Tair_max (time, lat, lon) float64 5TB dask.array\n", + " Tair_min (time, lat, lon) float64 5TB dask.array\n", + "Attributes: (12/17)\n", + " missing_value: -9999.0\n", + " time_definition: daily\n", + " shortname: NLDAS_FOR0010_D_3.0\n", + " title: NLDAS Forcing Data L4 Daily 0.01 x 0.01 degree V3...\n", + " version: 3.0 beta\n", + " institution: NASA GSFC\n", + " ... ...\n", + " websites: https://ldas.gsfc.nasa.gov/nldas/v3/ ; https://li...\n", + " MAP_PROJECTION: EQUIDISTANT CYLINDRICAL\n", + " SOUTH_WEST_CORNER_LAT: 7.005000114440918\n", + " SOUTH_WEST_CORNER_LON: -168.9949951171875\n", + " DX: 0.009999999776482582\n", + " DY: 0.009999999776482582" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(f\"\\nšŸ“Š Testing: VirtualiZarr + Icechunk\")\n", + "start = perf_counter()\n", + "storage = icechunk.s3_storage(\n", + " bucket='nasa-waterinsight',\n", + " prefix=f\"virtual-zarr-store/NLDAS-3-icechunk\",\n", + " region=\"us-west-2\",\n", + " anonymous=True,\n", + ")\n", + "\n", + "chunk_url = \"s3://nasa-waterinsight/NLDAS3/forcing/daily/\"\n", + "virtual_credentials = icechunk.containers_credentials({\n", + " chunk_url: icechunk.s3_anonymous_credentials()\n", + "})\n", + "\n", + "repo = icechunk.Repository.open(\n", + " storage=storage,\n", + " authorize_virtual_chunk_access=virtual_credentials,\n", + ")\n", + "\n", + "session = repo.readonly_session('main')\n", + "ds = xr.open_zarr(session.store, consolidated=False, zarr_format=3, chunks={})\n", + "record_timing('open', 'virtualzarr_icechunk', perf_counter() - start)\n", + "\n", + "benchmark_method(ds, 'virtualzarr_icechunk')\n", + "print_summary('virtualzarr_icechunk')\n", + "ds" + ] + }, + { + "cell_type": "markdown", + "id": "9c499d4a", + "metadata": {}, + "source": [ + "### Save Timings for This Environment" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "884f4f48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "āœ… Timings saved to cloud_data_timings.json\n", + " Environment: local\n", + " Timestamp: 2025-12-11 12:18:33\n", + "\n", + "šŸ“ Saved timing data available for: ['cloud', 'local']\n" + ] + } + ], + "source": [ + "# Save the timing results for this environment\n", + "save_current_timings()\n", + "\n", + "# Show what environments we have data for\n", + "saved_envs = get_saved_environments()\n", + "print(f\"\\nšŸ“ Saved timing data available for: {saved_envs}\")" + ] + }, + { + "cell_type": "markdown", + "id": "554035bf", + "metadata": {}, + "source": [ + "## šŸ†• Local vs. Cloud Performance Comparison\n", + "\n", + "After running this notebook in both environments, run this section to see the performance differences." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "dbce4bc9", + "metadata": {}, + "outputs": [], + "source": [ + "def create_comparison_report():\n", + " \"\"\"Create a comprehensive comparison between local and cloud runs.\"\"\"\n", + " all_timings = load_all_timings()\n", + " \n", + " if len(all_timings) < 2:\n", + " missing = []\n", + " if 'local' not in all_timings:\n", + " missing.append('local')\n", + " if 'cloud' not in all_timings:\n", + " missing.append('cloud')\n", + " print(f\"āš ļø Need timing data from both environments!\")\n", + " print(f\" Missing: {missing}\")\n", + " print(f\" Available: {list(all_timings.keys())}\")\n", + " print(f\"\\n To complete the comparison:\")\n", + " print(f\" 1. Run this notebook with ENVIRONMENT = 'local' on your local machine\")\n", + " print(f\" 2. Run this notebook with ENVIRONMENT = 'cloud' on cloud infrastructure (e.g., AWS us-west-2)\")\n", + " print(f\" 3. Copy the {TIMINGS_FILE} file between environments or manually combine results\")\n", + " return None\n", + " \n", + " return all_timings\n", + "\n", + "all_timings = create_comparison_report()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "41e19522", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "šŸ“Š Chart saved to 'local_vs_cloud_comparison.png'\n" + ] + } + ], + "source": [ + "def plot_comparison(all_timings):\n", + " \"\"\"Create a visual comparison of local vs cloud performance.\"\"\"\n", + " if all_timings is None:\n", + " return\n", + " \n", + " import matplotlib.pyplot as plt\n", + " \n", + " methods = ['fsspec_default_cache', 'fsspec_block_cache', 'virtualzarr_icechunk']\n", + " method_labels = ['fsspec\\n(default)', 'fsspec\\n(block)', 'VirtualiZarr\\n+ Icechunk']\n", + " \n", + " # Calculate total times for each method\n", + " local_totals = []\n", + " cloud_totals = []\n", + " \n", + " for method in methods:\n", + " local_total = sum(\n", + " all_timings.get('local', {}).get('timings', {}).get(op, {}).get(method, 0)\n", + " for op in ['open', 'spatial_subset_load', 'time_slice_load', 'timeseries_load']\n", + " )\n", + " cloud_total = sum(\n", + " all_timings.get('cloud', {}).get('timings', {}).get(op, {}).get(method, 0)\n", + " for op in ['open', 'spatial_subset_load', 'time_slice_load', 'timeseries_load']\n", + " )\n", + " local_totals.append(local_total)\n", + " cloud_totals.append(cloud_total)\n", + " \n", + " # Create bar chart\n", + " x = np.arange(len(methods))\n", + " width = 0.35\n", + " \n", + " fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))\n", + " \n", + " # Total time comparison\n", + " bars1 = ax1.bar(x - width/2, local_totals, width, label='Local', color='#ff6b6b', alpha=0.8)\n", + " bars2 = ax1.bar(x + width/2, cloud_totals, width, label='Cloud (in-region)', color='#4ecdc4', alpha=0.8)\n", + " \n", + " ax1.set_ylabel('Total Time (seconds)', fontsize=12)\n", + " ax1.set_title('Total Execution Time: Local vs Cloud', fontsize=14, fontweight='bold')\n", + " ax1.set_xticks(x)\n", + " ax1.set_xticklabels(method_labels, fontsize=10)\n", + " ax1.legend()\n", + " ax1.grid(axis='y', alpha=0.3)\n", + " \n", + " # Add value labels on bars\n", + " for bar in bars1:\n", + " height = bar.get_height()\n", + " ax1.annotate(f'{height:.1f}s',\n", + " xy=(bar.get_x() + bar.get_width() / 2, height),\n", + " xytext=(0, 3),\n", + " textcoords=\"offset points\",\n", + " ha='center', va='bottom', fontsize=9)\n", + " for bar in bars2:\n", + " height = bar.get_height()\n", + " ax1.annotate(f'{height:.1f}s',\n", + " xy=(bar.get_x() + bar.get_width() / 2, height),\n", + " xytext=(0, 3),\n", + " textcoords=\"offset points\",\n", + " ha='center', va='bottom', fontsize=9)\n", + " \n", + " # Speedup comparison\n", + " speedups = [l/c if c > 0 else 0 for l, c in zip(local_totals, cloud_totals)]\n", + " colors = ['#2ecc71' if s > 1 else '#e74c3c' for s in speedups]\n", + " bars3 = ax2.bar(x, speedups, color=colors, alpha=0.8)\n", + " ax2.axhline(y=1, color='gray', linestyle='--', alpha=0.7, label='No speedup')\n", + " ax2.set_ylabel('Speedup Factor (Local Time / Cloud Time)', fontsize=12)\n", + " ax2.set_title('Cloud Speedup by Method', fontsize=14, fontweight='bold')\n", + " ax2.set_xticks(x)\n", + " ax2.set_xticklabels(method_labels, fontsize=10)\n", + " ax2.grid(axis='y', alpha=0.3)\n", + " \n", + " # Add value labels\n", + " for bar, speedup in zip(bars3, speedups):\n", + " height = bar.get_height()\n", + " ax2.annotate(f'{speedup:.1f}x',\n", + " xy=(bar.get_x() + bar.get_width() / 2, height),\n", + " xytext=(0, 3),\n", + " textcoords=\"offset points\",\n", + " ha='center', va='bottom', fontsize=11, fontweight='bold')\n", + " \n", + " plt.tight_layout()\n", + " plt.savefig('local_vs_cloud_comparison.png', dpi=150, bbox_inches='tight')\n", + " plt.show()\n", + " \n", + " print(\"\\nšŸ“Š Chart saved to 'local_vs_cloud_comparison.png'\")\n", + "\n", + "if all_timings:\n", + " plot_comparison(all_timings)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a27f1458", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================================================================\n", + "šŸ’” KEY INSIGHTS\n", + "================================================================================\n", + "\n", + "šŸ“ˆ Average speedup from data-proximate computing: 3.1x\n", + "šŸ† Best speedup: fsspec_block_cache (4.2x faster on cloud)\n", + "\n", + "ā±ļø Total time (all methods):\n", + " Local: 2271.4 seconds\n", + " Cloud: 580.9 seconds\n", + " Saved: 1690.5 seconds (74% reduction)\n", + "\n", + "šŸŽÆ Key takeaway: Running your code on cloud infrastructure\n", + " co-located with your data can dramatically improve performance!\n" + ] + } + ], + "source": [ + "def print_key_insights(all_timings):\n", + " \"\"\"Print key insights from the comparison.\"\"\"\n", + " if all_timings is None:\n", + " return\n", + " \n", + " print(\"\\n\" + \"=\"*80)\n", + " print(\"šŸ’” KEY INSIGHTS\")\n", + " print(\"=\"*80)\n", + " \n", + " # Calculate average speedup\n", + " speedups = []\n", + " methods = ['fsspec_default_cache', 'fsspec_block_cache', 'virtualzarr_icechunk']\n", + " \n", + " for method in methods:\n", + " local_total = sum(\n", + " all_timings.get('local', {}).get('timings', {}).get(op, {}).get(method, 0)\n", + " for op in ['open', 'spatial_subset_load', 'time_slice_load', 'timeseries_load']\n", + " )\n", + " cloud_total = sum(\n", + " all_timings.get('cloud', {}).get('timings', {}).get(op, {}).get(method, 0)\n", + " for op in ['open', 'spatial_subset_load', 'time_slice_load', 'timeseries_load']\n", + " )\n", + " if cloud_total > 0:\n", + " speedups.append((method, local_total / cloud_total, local_total, cloud_total))\n", + " \n", + " if speedups:\n", + " avg_speedup = sum(s[1] for s in speedups) / len(speedups)\n", + " max_speedup = max(speedups, key=lambda x: x[1])\n", + " \n", + " print(f\"\\nšŸ“ˆ Average speedup from data-proximate computing: {avg_speedup:.1f}x\")\n", + " print(f\"šŸ† Best speedup: {max_speedup[0]} ({max_speedup[1]:.1f}x faster on cloud)\")\n", + " \n", + " # Time saved\n", + " total_local = sum(s[2] for s in speedups)\n", + " total_cloud = sum(s[3] for s in speedups)\n", + " time_saved = total_local - total_cloud\n", + " \n", + " print(f\"\\nā±ļø Total time (all methods):\")\n", + " print(f\" Local: {total_local:.1f} seconds\")\n", + " print(f\" Cloud: {total_cloud:.1f} seconds\")\n", + " print(f\" Saved: {time_saved:.1f} seconds ({(time_saved/total_local)*100:.0f}% reduction)\")\n", + " \n", + " print(\"\\nšŸŽÆ Key takeaway: Running your code on cloud infrastructure\")\n", + " print(\" co-located with your data can dramatically improve performance!\")\n", + "\n", + "if all_timings:\n", + " print_key_insights(all_timings)" + ] + }, + { + "cell_type": "markdown", + "id": "a3e7d59c", + "metadata": {}, + "source": [ + "## Takeaways\n", + "\n", + "- When working on the cloud, try to find computing resources that are \"in-region\" to the data you're working with.\n", + "- File formats matter - consider using virtual Zarr if your data are not already \"cloud-optimized\".\n", + "- File access patterns matter - the default arguments for reading data from the cloud may be very slow! You can customize the configuration for better performance.\n", + "- **šŸ†• Data-proximate computing can provide dramatic speedups compared to working locally!**" + ] + }, + { + "cell_type": "markdown", + "id": "60e8db4b", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "- [Cloud-Optimized Geospatial Formats Guide](https://guide.cloudnativegeo.org/)\n", + "- [Xarray Tutorial - Zarr in Cloud Object Storage](https://tutorial.xarray.dev/intermediate/remote_data/cmip6-cloud.html)\n", + "- [Xarray Tutorial - Access Patterns to Remote Data with fsspec](https://tutorial.xarray.dev/intermediate/remote_data/cmip6-cloud.html)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "04-data-in-the-cloud", + "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.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/modules/04-data-in-the-cloud/index.md b/modules/04-data-in-the-cloud/index.md deleted file mode 100644 index 0bbc4e3..0000000 --- a/modules/04-data-in-the-cloud/index.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -authors: - - name: "Max Jones" - affiliations: - - "Development Seed" - email: "max@developmentseed.org" - orcid: "0000-0003-0180-8928" - github: "maxrjones" ---- - -# ā˜ļø 4 - Data in the Cloud 101 - -:::{note} šŸ› Slides -:icon: false -:class: dropdown - - -::: diff --git a/modules/04-data-in-the-cloud/local_vs_cloud_comparison.png b/modules/04-data-in-the-cloud/local_vs_cloud_comparison.png new file mode 100644 index 0000000..74b5ffd Binary files /dev/null and b/modules/04-data-in-the-cloud/local_vs_cloud_comparison.png differ diff --git a/modules/04-data-in-the-cloud/pyproject.toml b/modules/04-data-in-the-cloud/pyproject.toml new file mode 100644 index 0000000..98728bb --- /dev/null +++ b/modules/04-data-in-the-cloud/pyproject.toml @@ -0,0 +1,41 @@ +[project] +name = "04-data-in-the-cloud" +version = "0.1.0" +description = "Dependencies for module 4 - Data in the Cloud" +readme = "README.md" +requires-python = ">=3.11,<3.13" +dependencies = [ + "xarray>=2025.7.1", + "pandas>=2.3.1", + "numpy>=2.2.6,<2.3", + "scipy>=1.16.1", + "netCDF4>=1.7.2", + "cftime>=1.6.4", + "bottleneck>=1.5.0", + "dask>=2025.7.0", + "distributed>=2025.7.0", + "matplotlib>=3.10.5", + "cartopy>=0.25.0", + "numbagg>=0.9.0", + "pint>=0.24.4", + "sparse>=0.17.0", + "flox>=0.10.4", + "h5netcdf>=1.6.4,<1.8", + "h5py>=3.14.0,<3.15", + "zarr>=3.1.1,<3.2", + "fsspec>=2025.7.0,<2025.11", + "cubed-xarray>=0.0.8", + "cubed[diagnostics]>=0.23.0", + "icechunk>=1.1.12", + "obspec-utils>=0.2.0", + "obstore>=0.8.2", + "s3fs>=2025.10.0", + "virtualizarr>=2.2.1", +] + +[dependency-groups] +dev = [ + "ipykernel>=7.1.0", + "jupytext>=1.18.1", + "ipython>=9.4.0,<9.5", +] diff --git a/myst.yml b/myst.yml index 6e2a459..784c362 100644 --- a/myst.yml +++ b/myst.yml @@ -30,6 +30,7 @@ project: children: - pattern: "for-instructors/*.md" + site: template: "book-theme" actions: diff --git a/pixi.lock b/pixi.lock index 7c95e3a..be9624a 100644 --- a/pixi.lock +++ b/pixi.lock @@ -8,32 +8,66 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-h5347b49_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.7.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.0-h5989046_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.10.0-h36edbcc_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.2-h32b2ec7_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.7-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h9a5124b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.7.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.10.0-h64c5147_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.2-h40d2674_100_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 @@ -64,14 +98,42 @@ packages: license_family: BSD size: 260341 timestamp: 1757437258798 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - sha256: 3b5ad78b8bb61b6cdc0978a6a99f8dfb2cc789a451378d054698441005ecbdb6 - md5: f9e5fbc24009179e8b0409624691758a +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + sha256: b456200636bd5fecb2bec63f7e0985ad2097cf1b83d60ce0b6968dffa6d02aa1 + md5: 58fd217444c2a5701a44244faf518206 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + size: 125061 + timestamp: 1757437486465 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e + md5: 920bb03579f15389b9e512095ad995b7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + size: 207882 + timestamp: 1765214722852 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + sha256: 2995f2aed4e53725e5efbc28199b46bf311c3cab2648fc4f10c2227d6d5fa196 + md5: bcb3cba70cf1eec964a03b4ba7775f01 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + size: 180327 + timestamp: 1765215064054 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + sha256: b986ba796d42c9d3265602bc038f6f5264095702dd546c14bc684e60c385e773 + md5: f0991f0f84902f6b6009b4d2350a83aa depends: - __unix license: ISC - size: 155907 - timestamp: 1759649036195 + size: 152432 + timestamp: 1762967197890 - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e md5: 8b189310083baabfb622af68fd9d3ae3 @@ -83,70 +145,183 @@ packages: license_family: MIT size: 12129203 timestamp: 1720853576813 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - sha256: 707dfb8d55d7a5c6f95c772d778ef07a7ca85417d9971796f7d3daad0b615de8 - md5: 14bae321b8127b63cba276bd53fac237 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda + sha256: 9ba12c93406f3df5ab0a43db8a4b4ef67a5871dfd401010fbe29b218b2cbe620 + md5: 5eb22c1d7b3fc4abb50d92d621583137 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + size: 11857802 + timestamp: 1720853997952 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_104.conda + sha256: 9e191baf2426a19507f1d0a17be0fdb7aa155cdf0f61d5a09c808e0a69464312 + md5: a6abd2796fc332536735f68ba23f7901 depends: - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 constrains: - - binutils_impl_linux-64 2.44 + - binutils_impl_linux-64 2.45 license: GPL-3.0-only license_family: GPL - size: 747158 - timestamp: 1758810907507 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 - md5: 4211416ecba1866fab0c6470986c22d6 + size: 725545 + timestamp: 1764007826689 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e + md5: 72c8fd1af66bd67bf580645b426513ed depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 + license: MIT + license_family: MIT + size: 79965 + timestamp: 1764017188531 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + sha256: a7cb9e660531cf6fbd4148cff608c85738d0b76f0975c5fc3e7d5e92840b7229 + md5: 006e7ddd8a110771134fcc4e1e3a6ffa + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + size: 79443 + timestamp: 1764017945924 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + sha256: 12fff21d38f98bc446d82baa890e01fd82e3b750378fedc720ff93522ffb752b + md5: 366b40a69f0ad6072561c1d09301c886 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + size: 34632 + timestamp: 1764017199083 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + sha256: 2eae444039826db0454b19b52a3390f63bfe24f6b3e63089778dd5a5bf48b6bf + md5: 079e88933963f3f149054eec2c487bc2 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + size: 29452 + timestamp: 1764017979099 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + sha256: a0c15c79997820bbd3fbc8ecf146f4fe0eca36cc60b62b63ac6cf78857f1dd0d + md5: 4ffbb341c8b616aa2494b6afb26a0c5f + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + size: 298378 + timestamp: 1764017210931 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + sha256: 01436c32bb41f9cb4bcf07dda647ce4e5deb8307abfc3abdc8da5317db8189d1 + md5: b2b7c8288ca1a2d71ff97a8e6a1e8883 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + size: 290754 + timestamp: 1764018009077 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.7-hf598326_0.conda + sha256: 4bdbef0241b52e7a8552e8af7425f0b56d5621dd69df46c816546fefa17d77ab + md5: 0de94f39727c31c0447e408c5a210a56 + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 568715 + timestamp: 1764676451068 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 + md5: 172bf1cd1ff8629f2b1179945ed45055 + depends: + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + size: 112766 + timestamp: 1702146165126 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + sha256: 95cecb3902fbe0399c3a7e67a5bed1db813e5ab0e22f4023a5e0f722f2cc214f + md5: 36d33e440c31857372a72137f78bacf5 + license: BSD-2-Clause + license_family: BSD + size: 107458 + timestamp: 1702146414478 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + sha256: 1e1b08f6211629cbc2efe7a5bca5953f8f6b3cae0eeb04ca4dacee1bd4e2db2f + md5: 8b09ae86839581147ef2e5c5e229d164 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.7.3.* + license: MIT + license_family: MIT + size: 76643 + timestamp: 1763549731408 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda + sha256: fce22610ecc95e6d149e42a42fbc3cc9d9179bd4eb6232639a60f06e080eec98 + md5: b79875dbb5b1db9a4a22a4520f918e1a + depends: + - __osx >=11.0 constrains: - - expat 2.7.1.* + - expat 2.7.3.* license: MIT license_family: MIT - size: 74811 - timestamp: 1752719572741 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - sha256: 764432d32db45466e87f10621db5b74363a9f847d2b8b1f9743746cd160f06ab - md5: ede4673863426c0883c0063d853bbd85 + size: 67800 + timestamp: 1763549994166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 + md5: 35f29eec58405aaf55e01cb470d8c26a depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT - size: 57433 - timestamp: 1743434498161 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda - sha256: 08f9b87578ab981c7713e4e6a7d935e40766e10691732bba376d4964562bcb45 - md5: c0374badb3a5d4b1372db28d19462c53 + size: 57821 + timestamp: 1760295480630 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda + sha256: 9b8acdf42df61b7bfe8bdc545c016c29e61985e79748c64ad66df47dbc2e295f + md5: 411ff7cd5d1472bba0f55c0faf04453b + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + size: 40251 + timestamp: 1760295839166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda + sha256: 6eed58051c2e12b804d53ceff5994a350c61baf117ec83f5f10c953a3f311451 + md5: 6d0363467e6ed84f11435eb309f2ff06 depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgomp 15.2.0 h767d61c_7 - - libgcc-ng ==15.2.0=*_7 + - libgcc-ng ==15.2.0=*_16 + - libgomp 15.2.0 he0feb66_16 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 822552 - timestamp: 1759968052178 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda - sha256: 2045066dd8e6e58aaf5ae2b722fb6dfdbb57c862b5f34ac7bfb58c40ef39b6ad - md5: 280ea6eee9e2ddefde25ff799c4f0363 + size: 1042798 + timestamp: 1765256792743 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda + sha256: 5f07f9317f596a201cc6e095e5fc92621afca64829785e483738d935f8cab361 + md5: 5a68259fac2da8f2ee6f7bfe49c9eb8b depends: - - libgcc 15.2.0 h767d61c_7 + - libgcc 15.2.0 he0feb66_16 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 29313 - timestamp: 1759968065504 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda - sha256: e9fb1c258c8e66ee278397b5822692527c5f5786d372fe7a869b900853f3f5ca - md5: f7b4d76975aac7e5d9e6ad13845f92fe + size: 27256 + timestamp: 1765256804124 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_16.conda + sha256: 5b3e5e4e9270ecfcd48f47e3a68f037f5ab0f529ccb223e8e5d5ac75a58fc687 + md5: 26c46f90d0e727e95c6c9498a33a09f3 depends: - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 447919 - timestamp: 1759967942498 + size: 603284 + timestamp: 1765256703881 - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8 md5: 1a580f7796c7bf6393fddb8bbbde58dc @@ -158,6 +333,16 @@ packages: license: 0BSD size: 112894 timestamp: 1749230047870 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda + sha256: 0cb92a9e026e7bd4842f410a5c5c665c89b2eb97794ffddba519a626b8ce7285 + md5: d6df911d4564d77c4374b02552cb17d1 + depends: + - __osx >=11.0 + constrains: + - xz 5.8.1.* + license: 0BSD + size: 92286 + timestamp: 1749230283517 - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda sha256: 3aa92d4074d4063f2a162cd8ecb45dccac93e543e565c01a787e16a43501f7ee md5: c7e925f37e3b40d893459e625f6a53f1 @@ -168,47 +353,94 @@ packages: license_family: BSD size: 91183 timestamp: 1748393666725 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - sha256: 6d9c32fc369af5a84875725f7ddfbfc2ace795c28f246dc70055a79f9b2003da - md5: 0b367fad34931cb79e0d6b7e5c06bb1c +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + sha256: 0a1875fc1642324ebd6c4ac864604f3f18f57fbcf558a8264f6ced028a3c75b2 + md5: 85ccccb47823dd9f7a99d2c7f530342f + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + size: 71829 + timestamp: 1748393749336 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + sha256: a4a7dab8db4dc81c736e9a9b42bdfd97b087816e029e221380511960ac46c690 + md5: b499ce4b026493a13774bcf0f4c33849 depends: - __glibc >=2.17,<3.0.a0 + - c-ares >=1.34.5,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.2,<4.0a0 + license: MIT + license_family: MIT + size: 666600 + timestamp: 1756834976695 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda + sha256: a07cb53b5ffa2d5a18afc6fd5a526a5a53dd9523fbc022148bd2f9395697c46d + md5: a4b4dd73c67df470d091312ab87bf6ae + depends: + - __osx >=11.0 + - c-ares >=1.34.5,<2.0a0 + - libcxx >=19 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.2,<4.0a0 + license: MIT + license_family: MIT + size: 575454 + timestamp: 1756835746393 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_0.conda + sha256: 6f0e8a812e8e33a4d8b7a0e595efe28373080d27b78ee4828aa4f6649a088454 + md5: 2e1b84d273b01835256e53fd938de355 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: blessing + size: 938979 + timestamp: 1764359444435 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h9a5124b_0.conda + sha256: a46b167447e2a9e38586320c30b29e3b68b6f7e6b873c18d6b1aa2efd2626917 + md5: 67e50e5bd4e5e2310d66b88c4da50096 + depends: + - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: blessing - size: 932581 - timestamp: 1753948484112 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda - sha256: 1b981647d9775e1cdeb2fab0a4dd9cd75a6b0de2963f6c3953dbd712f78334b3 - md5: 5b767048b1b3ee9a954b06f4084f93dc + size: 906292 + timestamp: 1764359907797 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda + sha256: 813427918316a00c904723f1dfc3da1bbc1974c5cfe1ed1e704c6f4e0798cbc6 + md5: 68f68355000ec3f1d6f26ea13e8f525f depends: - __glibc >=2.17,<3.0.a0 - - libgcc 15.2.0 h767d61c_7 + - libgcc 15.2.0 he0feb66_16 constrains: - - libstdcxx-ng ==15.2.0=*_7 + - libstdcxx-ng ==15.2.0=*_16 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 3898269 - timestamp: 1759968103436 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda - sha256: 024fd46ac3ea8032a5ec3ea7b91c4c235701a8bf0e6520fe5e6539992a6bd05f - md5: f627678cf829bd70bccf141a19c3ad3e + size: 5856456 + timestamp: 1765256838573 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda + sha256: 81f2f246c7533b41c5e0c274172d607829019621c4a0823b5c0b4a8c7028ee84 + md5: 1b3152694d236cf233b76b8c56bf0eae depends: - - libstdcxx 15.2.0 h8f9b012_7 + - libstdcxx 15.2.0 h934c35e_16 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 29343 - timestamp: 1759968157195 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda - sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808 - md5: 80c07c68d2f6870250959dcc95b209d1 + size: 27300 + timestamp: 1765256885128 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-h5347b49_1.conda + sha256: 030447cf827c471abd37092ab9714fde82b8222106f22fde94bc7a64e2704c40 + md5: 41f5c09a211985c3ce642d60721e7c3e depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 license: BSD-3-Clause license_family: BSD - size: 37135 - timestamp: 1758626800002 + size: 40235 + timestamp: 1764790744114 - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b md5: 0f03292cc56bf91a077a134ea8747118 @@ -219,6 +451,15 @@ packages: license_family: MIT size: 895108 timestamp: 1753948278280 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda + sha256: 042c7488ad97a5629ec0a991a8b2a3345599401ecc75ad6a5af73b60e6db9689 + md5: c0d87c3c8e075daf1daf6c31b53e8083 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + size: 421195 + timestamp: 1753948426421 - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 md5: edb0dca6bc32e4f4789199455a1dbeb8 @@ -231,6 +472,17 @@ packages: license_family: Other size: 60963 timestamp: 1727963148474 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b + md5: 369964e85dc26bfe78f41399b366c435 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + size: 46438 + timestamp: 1727963202283 - conda: https://conda.anaconda.org/conda-forge/noarch/mystmd-1.7.0-pyhcf101f3_0.conda sha256: 11c8fdb494493e636024696395cbf5271f4890a1d69009daa4df17128a8bf792 md5: 8c0f1ed376697206261d1b99bd4858b6 @@ -239,6 +491,7 @@ packages: - nodejs >=18 - python license: MIT + license_family: MIT size: 2163398 timestamp: 1764855843630 - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda @@ -250,46 +503,91 @@ packages: license: X11 AND BSD-3-Clause size: 891641 timestamp: 1738195959188 -- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda - sha256: 6abb823fd4d28e6474f40dfcf38e772e5869ee755be855cf5d2c0d49f888c75e - md5: 8a2a73951c1ea275e76fb1b92d97ff3e +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733 + md5: 068d497125e4bf8a66bf707254fff5ae + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + size: 797030 + timestamp: 1738196177597 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.10.0-h36edbcc_2.conda + sha256: 3c1d59afd09c52dac76eed71b1e3b8d4b8502db151745216c036ad57808b82bb + md5: e2484efbb090278c0070dee87d9cdb21 depends: - __glibc >=2.28,<3.0.a0 - libstdcxx >=14 - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + - icu >=75.1,<76.0a0 + - c-ares >=1.34.5,<2.0a0 - libuv >=1.51.0,<2.0a0 - - openssl >=3.5.3,<4.0a0 + - openssl >=3.5.4,<4.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libsqlite >=3.51.1,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + - libnghttp2 >=1.67.0,<2.0a0 + license: MIT + size: 23504771 + timestamp: 1765210770135 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.10.0-h64c5147_2.conda + sha256: d85fd71ed029ed2d8df9eeb05061c22a4e595a9d2634ee7abc5efa6bcde8f3c6 + md5: f3573bab908a11e6ed14265832779de6 + depends: + - __osx >=11.0 + - libcxx >=19 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libsqlite >=3.51.1,<4.0a0 + - openssl >=3.5.4,<4.0a0 + - c-ares >=1.34.5,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 - icu >=75.1,<76.0a0 - libzlib >=1.3.1,<2.0a0 + - libnghttp2 >=1.67.0,<2.0a0 + - libuv >=1.51.0,<2.0a0 license: MIT license_family: MIT - size: 25557455 - timestamp: 1759064044872 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - sha256: e807f3bad09bdf4075dbb4168619e14b0c0360bacb2e12ef18641a834c8c5549 - md5: 14edad12b59ccbfa3910d42c72adc2a0 + size: 16182130 + timestamp: 1765047982974 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + sha256: a47271202f4518a484956968335b2521409c8173e123ab381e775c358c67fe6d + md5: 9ee58d5c534af06558933af3c845a780 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates - libgcc >=14 license: Apache-2.0 license_family: Apache - size: 3119624 - timestamp: 1759324353651 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.0-h5989046_101_cp314.conda - build_number: 101 - sha256: 61ae2c29b1097c12161a09a4061be8f909bc1387d8388e875d8ed5e357ef0824 - md5: b2ad21488149ec2c4d83640619de2430 + size: 3165399 + timestamp: 1762839186699 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda + sha256: ebe93dafcc09e099782fe3907485d4e1671296bc14f8c383cb6f3dfebb773988 + md5: b34dc4172653c13dcf453862f251af2b + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + size: 3108371 + timestamp: 1762839712322 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.2-h32b2ec7_100_cp314.conda + build_number: 100 + sha256: a120fb2da4e4d51dd32918c149b04a08815fd2bd52099dad1334647984bb07f1 + md5: 1cef1236a05c3a98f68c33ae9425f656 depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.1,<3.0a0 - - libffi >=3.4.6,<3.5.0a0 + - libexpat >=2.7.3,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - liblzma >=5.8.1,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.50.4,<4.0a0 + - libsqlite >=3.51.1,<4.0a0 - libuuid >=2.41.2,<3.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 @@ -300,8 +598,32 @@ packages: - tzdata - zstd >=1.5.7,<1.6.0a0 license: Python-2.0 - size: 36692257 - timestamp: 1760299587505 + size: 36790521 + timestamp: 1765021515427 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.2-h40d2674_100_cp314.conda + build_number: 100 + sha256: 1a93782e90b53e04c2b1a50a0f8bf0887936649d19dba6a05b05c4b44dae96b7 + md5: 14f15ab0d31a2ee5635aa56e77132594 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.3,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.1,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.51.1,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + size: 13575758 + timestamp: 1765021280625 python_site_packages_path: lib/python3.14/site-packages - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda build_number: 8 @@ -323,32 +645,61 @@ packages: license_family: GPL size: 282480 timestamp: 1740379431762 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1 - md5: a0116df4f4ed05c303811a837d5b39d8 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda + sha256: 7db04684d3904f6151eff8673270922d31da1eea7fa73254d01c437f49702e34 + md5: 63ef3f6e6d6d5c589e64f11263dc5676 + depends: + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 252359 + timestamp: 1740379663071 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + sha256: 1544760538a40bcd8ace2b1d8ebe3eb5807ac268641f8acdc18c69c5ebfeaf64 + md5: 86bc20552bf46075e3d92b67f089172d depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 license: TCL license_family: BSD - size: 3285204 - timestamp: 1748387766691 + size: 3284905 + timestamp: 1763054914403 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + sha256: ad0c67cb03c163a109820dc9ecf77faf6ec7150e942d1e8bb13e5d39dc058ab7 + md5: a73d54a5abba6543cb2f0af1bfbd6851 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + size: 3125484 + timestamp: 1763055028377 - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192 md5: 4222072737ccff51314b5ece9c7d6f5a license: LicenseRef-Public-Domain size: 122968 timestamp: 1742727099393 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb - md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 + md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD - size: 567578 - timestamp: 1742433379869 + size: 601375 + timestamp: 1764777111296 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + sha256: 9485ba49e8f47d2b597dd399e88f4802e100851b27c21d7525625b0b4025a5d9 + md5: ab136e4c34e97f34fb621d2592a393d8 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + size: 433413 + timestamp: 1764777166076 diff --git a/pixi.toml b/pixi.toml index 6ba12fc..07f2afd 100644 --- a/pixi.toml +++ b/pixi.toml @@ -2,7 +2,7 @@ authors = [] channels = ["conda-forge"] name = "workshop-open-source-geospatial" -platforms = ["linux-64"] +platforms = ["linux-64", "osx-arm64"] version = "0.1.0" [tasks]