[project]
name = "research_notebook"
version = "0.1.0"
description = "A scientific research project template"
channels = ["conda-forge"]
platforms = ["linux-64", "osx-arm64"]

[dependencies]
python = ">=3.12"
numpy = "*"
scipy = "*"
xarray = "*"
netcdf4 = "*"
matplotlib = "*"
dvc = ">=3"
hydra-core = ">=1.3"

[pypi-dependencies]
research_notebook = { path = ".", editable = true }
hydra-zen = ">=0.13"
omegaconf = ">=2.3"

[feature.dev.dependencies]
pytest = ">=8"
pytest-cov = ">=5"
pre-commit = ">=3.7"
ruff = ">=0.9"
jupytext = ">=1.16"

[feature.dev.pypi-dependencies]
ty = ">=0.0.1a6"

[feature.docs.dependencies]
nodejs = ">=18"

[feature.docs.pypi-dependencies]
mystmd = "*"

[feature.jupyterlab.dependencies]
jupyterlab = ">=4"
ipykernel = ">=6.29"
# ── git / diff / format ──────────────────────────────────────────────────────
jupyterlab-git = "*"
nbdime = "*"
black = ">=24"
isort = ">=5"
# ── language server (LSP) ────────────────────────────────────────────────────
jupyterlab-lsp = "*"
jupyter-lsp = "*"
python-lsp-server = "*"
# ── interactive widgets & plotting ───────────────────────────────────────────
jupyterlab-myst = "*"
ipywidgets = ">=8"
ipympl = ">=0.9"          # %matplotlib widget — interactive matplotlib figures
plotly = ">=5"            # interactive charts
# ── resource & GPU monitoring ────────────────────────────────────────────────
jupyter-resource-usage = "*"      # CPU / RAM panel + status bar badge
jupyterlab-nvdashboard = "*"      # NVIDIA GPU utilisation / memory dashboard
# ── dask ─────────────────────────────────────────────────────────────────────
dask = "*"                        # core dask (arrays, dataframes, delayed)
distributed = "*"                 # dask distributed scheduler
dask-labextension = "*"           # task stream, progress, worker memory panels
# ── data stack (marimo-compatible) ───────────────────────────────────────────
duckdb = ">=1"            # in-process analytical SQL
polars = ">=1"            # fast dataframes
pyarrow = "*"             # columnar memory + parquet I/O
altair = ">=5"            # declarative Vega-Lite charts
anywidget = "*"           # custom widget protocol (used by marimo + others)
# ── dashboarding & linked plots ───────────────────────────────────────────────
holoviews = ">=1.19"      # declarative linked interactive plots (hvPlot backend)
geoviews = ">=1.12"       # geographic HoloViews — choropleth, tiles, projections
panel = ">=1.4"           # reactive dashboarding; HoloViews / GeoViews server
hvplot = ">=0.10"         # high-level .hvplot() API over pandas/xarray/polars
# ── geospatial widgets & extensions ──────────────────────────────────────────
ipyleaflet = ">=0.19"    # interactive Leaflet map widget (pan, zoom, layers)
folium = ">=0.18"        # Leaflet maps rendered to static HTML in cells
leafmap = ">=0.40"       # high-level geo analysis + STAC browsing on ipyleaflet
jupytergis = ">=0.5"     # QGIS-style layer panel, reads .qgz/.jGIS, collaborative editing
keplergl = ">=0.3"       # GPU-accelerated large point-cloud + temporal visualization

[feature.jupyterlab.pypi-dependencies]
# ── data stack ───────────────────────────────────────────────────────────────
vegafusion = {version = ">=2", extras = ["embed"]}  # server-side Vega-Lite transforms for Altair
# ── code quality ─────────────────────────────────────────────────────────────
jupyterlab-code-formatter = "*"
jupyterlab-spellchecker = "*"
python-lsp-ruff = "*"          # ruff plugin for python-lsp-server (lint + fix)
# ── productivity ─────────────────────────────────────────────────────────────
jupyterlab-execute-time = "*"  # shows execution time below each cell
jupyterlab-geojson = "*"       # renders .geojson files as interactive maps in the file browser
# ── inspection ───────────────────────────────────────────────────────────────
lckr-jupyterlab-variableinspector = "*"   # variable inspector panel
# ── notebook metadata ────────────────────────────────────────────────────────
watermark = "*"                # %watermark magic: package versions + hw info
# ── AI assistant ─────────────────────────────────────────────────────────────
jupyter-ai = "*"               # chat panel + %%ai magic; supports Claude via langchain-anthropic
langchain-anthropic = "*"      # Anthropic/Claude backend for jupyter-ai

[feature.marimo.dependencies]
marimo = "*"
duckdb = ">=1"
altair = ">=5"
polars = ">=1"
pyarrow = "*"
anywidget = "*"

[feature.marimo.pypi-dependencies]
vegafusion = {version = ">=2", extras = ["embed"]}
plotly = ">=5"

# ---------------------------------------------------------------------------
# Showcase env: pyrox
# Pulls pyrox from its git main + the [colab] and [optax] extras so every
# notebook under notebooks/pyrox/ and projects/gaussian_processes/notebooks/
# can be re-executed end-to-end. The latter adds cartopy (Iberian maps in
# the Kronecker-GEV notebook); the rest of its deps overlap with pyrox's
# existing transitive closure (matplotlib, scipy via sklearn, optax).
# ---------------------------------------------------------------------------
[feature.pyrox.dependencies]
python = "3.12.*"
ipykernel = ">=6.29"
jupyter = "*"
nbconvert = "*"
# cartopy pulls geos/proj/shapely via conda-forge — much cleaner than PyPI
# wheels for the C-library-heavy geospatial stack. Used by
# projects/gaussian_processes/notebooks/13_applied/spatial_extremes/.
cartopy = ">=0.24"

[feature.pyrox.pypi-dependencies]
# Pinned to specific commits (rather than branch = "main") so the lockfile
# has stable git refs. Bump these when you want newer pyrox / xtremax.
pyrox = { git = "https://github.com/jejjohnson/pyrox", rev = "c122a87544d244192db622731d24b4a648eba853", extras = ["colab", "optax"] }
# xtremax provides the extreme-value distributions + primitives used by the
# 13_applied/spatial_extremes notebooks; its jax<0.11 pin co-installs with
# pyrox/gaussx on jax 0.10.
xtremax = { git = "https://github.com/jejjohnson/xtremax", rev = "ba79b377dc8e0abb1e033b1825797cf20fc47e81" }
# gaussx 0.0.14 (released 2026-05-05) requires jaxlib>=0.10; pyrox at the
# pinned rev pulls gaussx transitively. numpyro 0.21 (with the xla_pmap_p
# import fix) is now on PyPI, so the prior `<0.9` cap can be dropped.
jax = ">=0.10"
jaxlib = ">=0.10"
numpyro = ">=0.21"

[feature.pyrox.tasks]
execute-pyrox = "jupyter nbconvert --to notebook --execute --inplace notebooks/pyrox/gp/*.ipynb notebooks/pyrox/masterclass/*.ipynb notebooks/pyrox/ensembles/*.ipynb --ExecutePreprocessor.timeout=600"
execute-gaussian-processes = "jupyter nbconvert --to notebook --execute --inplace projects/gaussian_processes/notebooks/13_applied/spatial_extremes/*.ipynb --ExecutePreprocessor.timeout=1200"

# ---------------------------------------------------------------------------
# Showcase env: coordax
# Coordinate-aware JAX arrays. Notebooks 08-10 use diffrax (ODE/PDE solves)
# and optax + equinox (parameter/state estimation), so those are bundled in
# alongside coordax itself. No git pin needed — coordax is on PyPI.
# ---------------------------------------------------------------------------
[feature.coordax.dependencies]
python = "3.12.*"
ipykernel = ">=6.29"
jupyter = "*"
nbconvert = "*"
jupytext = ">=1.16"

[feature.coordax.pypi-dependencies]
coordax = ">=0.2"
# coordax unconditionally imports chex at package init (coordax/testing.py),
# but does not declare it as a runtime dep. Pin it here until coordax fixes
# the missing declaration or guards the import.
chex = ">=0.1"
jax = ">=0.5"
jaxlib = ">=0.5"
diffrax = ">=0.5"
optax = ">=0.2"
equinox = ">=0.11"

[feature.coordax.tasks]
execute-coordax = "jupyter nbconvert --to notebook --execute --inplace notebooks/coordax/foundations/*.ipynb notebooks/coordax/derivatives/*.ipynb notebooks/coordax/dynamics/*.ipynb --ExecutePreprocessor.timeout=600"

# ---------------------------------------------------------------------------
# Project env: methane-pod
# Self-contained subproject living under projects/methane_pod/. Bundles
# numpyro + jax so MCMC fits and Monte Carlo simulations from the POD +
# TMTPP notebooks can be re-executed end-to-end.
# ---------------------------------------------------------------------------
[feature.methane-pod.dependencies]
python = "3.12.*"
ipykernel = ">=6.29"
jupyter = "*"
nbconvert = "*"
jupytext = ">=1.16"

[feature.methane-pod.pypi-dependencies]
methane_pod = { path = "projects/methane_pod", editable = true }
# Clamp jax below the 0.9 line to match numpyro's xla_pmap_p import (same
# reasoning as the pyrox feature above). Drop this pin when numpyro ≥0.21
# lands on PyPI with the updated import.
jax = ">=0.7,<0.9"
jaxlib = ">=0.7,<0.9"
numpyro = ">=0.16"
equinox = ">=0.11"
corner = ">=2.2"
pandas = ">=2"

[feature.methane-pod.tasks]
execute-methane-pod = "jupyter nbconvert --to notebook --execute --inplace projects/methane_pod/notebooks/*.ipynb --ExecutePreprocessor.timeout=1200"
test-methane-pod = "pytest projects/methane_pod/tests -v -m 'not slow'"
test-methane-pod-all = "pytest projects/methane_pod/tests -v"

# ---------------------------------------------------------------------------
# Project env: plume-simulation
# Self-contained sub-project under projects/plume_simulation/. Bundles
# numpyro + jax for the Bayesian inference layer and xarray for the
# forward-simulation helper.
# ---------------------------------------------------------------------------
[feature.plume-simulation.dependencies]
python = "3.12.*"
ipykernel = ">=6.29"
jupyter = "*"
nbconvert = "*"

[feature.plume-simulation.pypi-dependencies]
plume_simulation = { path = "projects/plume_simulation", editable = true }
# Same numpyro-0.9-line compatibility clamp as the pyrox / methane-pod envs.
jax = ">=0.7,<0.9"
jaxlib = ">=0.7,<0.9"
numpyro = ">=0.16"
xarray = "*"
matplotlib = ">=3.8"
# diffrax is used by the gauss_puff sub-model for time-varying wind
# cumulative-integral solves under the Gaussian puff forward model.
diffrax = ">=0.6"
equinox = ">=0.11"
# gaussx supplies the structured-linear-algebra primitives (LowRankUpdate
# operator + Woodbury-backed solve) used by the radtran matched-filter
# retrieval. With a few-hundred-channel hyperspectral target and a rank-16
# covariance, the operator-based path is ~30× faster than materialising
# Σ⁻¹ via a dense inverse. Pulled by git commit for lockfile reproducibility.
gaussx = { git = "https://github.com/jejjohnson/gaussx", rev = "fd9a2427" }
# finitevolx supplies the Arakawa C-grid FVM operators used by the
# les_fvm Eulerian dispersion sub-model (3D advection-diffusion of a
# passive methane tracer under a prescribed wind field). Pulled from
# git by tag so the lockfile is reproducible — bump when a new tag
# is released.
finitevolx = { git = "https://github.com/jejjohnson/finitevolX", tag = "v0.0.41" }
# hitran-api (HAPI) powers the hapi_lut sub-package: line-by-line Voigt
# absorption cross-section LUTs. Imported lazily inside the generator, so
# the rest of plume_simulation does not require it at import time. The
# hapi_lut notebooks need to download HITRAN line parameters on first run
# (tens of MB per gas, cached under projects/plume_simulation/data/hitran_cache/).
hitran-api = "*"

[feature.plume-simulation.tasks]
execute-plume-simulation = "jupyter nbconvert --to notebook --execute --inplace projects/plume_simulation/notebooks/*.ipynb projects/plume_simulation/notebooks/gauss_puff/*.ipynb projects/plume_simulation/notebooks/les_fvm/*.ipynb --ExecutePreprocessor.timeout=1200"
# The hapi_lut and radtran notebooks are kept out of the CI-targeted task
# above because they depend on the HITRAN line parameters fetched by hapi_lut
# (network) and the uncommitted .nc LUTs under projects/plume_simulation/data/.
# Run locally via the tasks below.
execute-plume-hapi-lut = "jupyter nbconvert --to notebook --execute --inplace projects/plume_simulation/notebooks/hapi_lut/*.ipynb --ExecutePreprocessor.timeout=1800"
execute-plume-radtran = "jupyter nbconvert --to notebook --execute --inplace projects/plume_simulation/notebooks/radtran/*.ipynb --ExecutePreprocessor.timeout=1800"
test-plume-simulation = "pytest projects/plume_simulation/tests -v -m 'not slow'"
test-plume-simulation-all = "pytest projects/plume_simulation/tests -v"

# ---------------------------------------------------------------------------
# Project env: geostack
# Notebook-only walkthrough of the pipekit + geotoolz + geopatcher stack
# (see projects/geostack/README.md for the chronological order). The
# geotoolz / geopatcher / pipekit trio is not on PyPI yet, so the
# editable geostack pyproject pulls them from GitHub.
# ---------------------------------------------------------------------------
[feature.geostack.dependencies]
python = "3.12.*"
ipykernel = ">=6.29"
jupyter = "*"
nbconvert = "*"
jupytext = ">=1.16"

[feature.geostack.pypi-dependencies]
geostack = { path = "projects/geostack", editable = true }
# geotoolz[patch] + planetary-computer + pystac-client + rioxarray +
# matplotlib come through the geostack pyproject's dependency stanza.

[feature.geostack.tasks]
# Runs the applied walkthrough (01–07) end-to-end. Notebooks 03–06
# fetch Sentinel-2 scenes from Microsoft Planetary Computer
# (anonymous read), so a network connection is required. 1800 s
# timeout per notebook covers the slower MPC reads (post-fire
# scene in 04 + 4-band scene in 06).
execute-geostack = "jupyter nbconvert --to notebook --execute --inplace projects/geostack/notebooks/*.ipynb --ExecutePreprocessor.timeout=1800"
# Runs the patching deep dives + recipes against real MPC data.
execute-geostack-patching = "jupyter nbconvert --to notebook --execute --inplace projects/geostack/notebooks/patching/*.ipynb projects/geostack/notebooks/patching/recipes/*.ipynb --ExecutePreprocessor.timeout=1800"
# Runs the catalog deep dives against real MPC + Natural Earth.
execute-geostack-catalog = "jupyter nbconvert --to notebook --execute --inplace projects/geostack/notebooks/catalog/*.ipynb --ExecutePreprocessor.timeout=1800"
# Convenience: applied + patching + catalog in one shot.
execute-geostack-all = { depends-on = ["execute-geostack", "execute-geostack-patching", "execute-geostack-catalog"] }
# Smoke-test the data loaders against the live external APIs.
test-geostack = "pytest projects/geostack/tests/ -v -m slow"

# ---------------------------------------------------------------------------
# Showcase env: satellite-viewer
# Three presentation layers (Panel + Streamlit + Jupyter notebook) over
# one shared STAC discovery core. Used to preview which scenes intersect
# an AOI before deciding what to download.
# ---------------------------------------------------------------------------
[feature.satellite-viewer.dependencies]
python = "3.12.*"
# Cap below 7: the satellite_viewer[panel] extra pulls ipywidgets-bokeh (for
# bokeh>=3.5), which requires ipykernel<7. Without the cap the conda solve pins
# ipykernel 7.x and the pypi resolve for this env becomes unsatisfiable.
ipykernel = ">=6.29,<7"
jupyter = "*"
jupytext = ">=1.16"

[feature.satellite-viewer.pypi-dependencies]
satellite_viewer = { path = "projects/satellite_viewer", editable = true, extras = ["panel", "notebook", "streamlit"] }

# Source repo-root .env on env activation so shell-level tools
# (earthengine, gcloud, aws, ...) and pixi tasks pick up the
# geospatial-service credentials documented in .env.example. The
# script is a no-op if .env is missing — the Python credentials module
# still works via env vars set elsewhere or service-native files.
[feature.satellite-viewer.activation]
scripts = ["projects/satellite_viewer/scripts/load_env.sh"]

[feature.satellite-viewer.tasks]
# Launch the Panel subapp (forward port 5006 in your editor). The
# websocket-origin allowlist is restricted to localhost — when port-
# forwarding from a remote container the browser still hits
# localhost:5006, so this keeps cross-site WebSocket hijacking off
# without breaking the typical local workflow.
panel-app = "panel serve projects/satellite_viewer/apps/panel_app.py --port 5006 --allow-websocket-origin=localhost:5006 --show"
# Launch the Streamlit subapp (forward port 8501 in your editor).
streamlit-app = "streamlit run projects/satellite_viewer/apps/streamlit_app.py --server.port 8501 --server.headless true"
# Launch JupyterLab so you can open notebooks/viewer.py via jupytext.
lab = "jupyter lab --no-browser --port 8888"
# Offline registry sanity checks.
test-satellite-viewer = "pytest projects/satellite_viewer/tests/ -v"

[environments]
default = { features = ["dev"], solve-group = "default" }
docs = { features = ["docs"], solve-group = "default" }
jupyterlab = { features = ["dev", "jupyterlab"], solve-group = "default" }
marimo = { features = ["dev", "marimo"], solve-group = "default" }
pyrox = { features = ["pyrox"], solve-group = "pyrox" }
coordax = { features = ["coordax"], solve-group = "coordax" }
methane-pod = { features = ["methane-pod", "dev"], solve-group = "methane-pod" }
plume-simulation = { features = ["plume-simulation", "dev"], solve-group = "plume-simulation" }
geostack = { features = ["geostack", "dev"], solve-group = "geostack" }
satellite-viewer = { features = ["satellite-viewer", "dev"], solve-group = "satellite-viewer" }

[tasks]
test = "pytest -v"
lint = "ruff check src/"
format = "ruff format src/"
typecheck = "ty check src/"
precommit = "pre-commit run --all-files"
train = "python scripts/train.py"
train-zen = "python scripts/train_zen.py"
evaluate = "python scripts/evaluate.py"
preprocess = "python scripts/preprocess.py"

[feature.docs.tasks]
docs-serve = "myst start --port 3000 --server-port 3100"
docs-build = "myst build --html"

[feature.jupyterlab.tasks]
# Forward port 8888 in VS Code SSH (Ports panel), then open the URL printed
# on stdout — Jupyter's default token-based auth stays enabled. To disable
# auth for a trusted local-only setup, run with --NotebookApp.token='' opt-in.
lab = "jupyter lab --no-browser --port 8888"

[feature.marimo.tasks]
marimo-edit = "marimo edit"
