Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
367 changes: 326 additions & 41 deletions Notebooks/T40_Deep_Time_Paleoclimate_Snapshots.ipynb

Large diffs are not rendered by default.

236 changes: 158 additions & 78 deletions Notebooks/T41_cGENIE_SST_vs_Proxies.ipynb

Large diffs are not rendered by default.

406 changes: 406 additions & 0 deletions Notebooks/T43_PLASIM_GENIE_SAT_On_Permian_Reconstruction.ipynb

Large diffs are not rendered by default.

361 changes: 0 additions & 361 deletions Notebooks/T43_SCION_SAT_On_Permian_Reconstruction.ipynb

This file was deleted.

559 changes: 559 additions & 0 deletions Notebooks/T44_PLASIM_GENIE_CO2_Sensitivity_252Ma.ipynb

Large diffs are not rendered by default.

337 changes: 0 additions & 337 deletions Notebooks/T44_SCION_CO2_Sensitivity_252Ma.ipynb

This file was deleted.

636 changes: 482 additions & 154 deletions Notebooks/T45_pySCION_Phanerozoic_Biogeochemistry.ipynb

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions Notebooks/paleoclimate_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Shared display helpers for the paleoclimate-cluster notebooks (T40–T45).

These functions clean up how a coarse climate-model field on a lat/lon grid is
drawn with ``pygmt.grdimage`` in a Mollweide projection. They are display-only:
they wrap/clamp/resample a field for *plotting* and never change the underlying
data used for analysis.

Used by T40 (deep-time SAT snapshots) and T41 (cGENIE SST vs proxies); import
with, e.g.::

from paleoclimate_helpers import close_lon_seam, clamp_lat_poles, refine_for_plot

Requires ``numpy`` and ``xarray``.
"""

import numpy as np
import xarray as xr


def close_lon_seam(da):
"""Pad a −180→180 grid cyclically so grdimage reaches the ±180 seam.

Climate-model SAT/SST grids typically stop short of the +180 meridian (a
3.75° grid's last column is at +176.25°), so grdimage leaves an unpainted
wedge at the antimeridian where the Mollweide boundary curves past the last
data column (GMT fills it grey). Wrapping one column round each seam lets the
raster reach the edge; the extra half-cells fall outside ``region="d"`` and
are clipped by the projection boundary.
"""
lon = da.lon.values
dlon = float(lon[1] - lon[0])
west = da.isel(lon=-1).assign_coords(lon=lon[0] - dlon) # +180 side → west edge
east = da.isel(lon=0).assign_coords(lon=lon[-1] + dlon) # −180 side → east edge
return xr.concat([west, da, east], dim="lon").sortby("lon")


def clamp_lat_poles(da):
"""Extend the outermost lat rows out to ±90 (latitude can't wrap, so clamp).

A grid that stops short of the poles would leave NaN slivers there when
interpolated onto a finer grid. Repeating the edge rows out to ±90 keeps the
interpolation in-range. Only adds a pole row when the grid actually stops
short — a grid that already reaches −90 would get a duplicated row, making
the latitude index non-unique (which breaks ``interp``).
"""
lat = da.lat.values
parts = [da]
if float(lat[0]) > -90.0:
parts.insert(0, da.isel(lat=0).assign_coords(lat=-90.0))
if float(lat[-1]) < 90.0:
parts.append(da.isel(lat=-1).assign_coords(lat=90.0))
return xr.concat(parts, dim="lat").sortby("lat") if len(parts) > 1 else da


def refine_for_plot(da, step=0.5):
"""Resample a coarse field onto a fine regular grid for display.

grdimage draws each native model cell as a quadrilateral, which staircases
along the curved Mollweide rim at high latitudes (and aliases into stray
cells right at the ±180 seam). Wrapping the seam, clamping the poles, and
bilinearly resampling onto a fine (default 0.5°) grid before plotting gives a
smooth rim. Display-only: it linearly interpolates a continuous field and
changes no analysis.

The final ``.transpose("lat", "lon")`` is essential: the concat/sortby steps
can silently reorder the dims to (lon, lat), and pygmt infers the plot region
from dim order — a transposed grid renders with lon/lat swapped (the data
collapses to a central blob). Forcing (lat, lon) keeps grdimage correct.
"""
src = clamp_lat_poles(close_lon_seam(da))
lon = np.arange(-180.0, 180.0 + step / 2, step)
lat = np.arange(-90.0, 90.0 + step / 2, step)
return src.interp(lon=lon, lat=lat, method="linear").transpose("lat", "lon")
95 changes: 95 additions & 0 deletions claude_palaeoclimate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Palaeoclimate cluster (T40–T45) — change summary

Notes on a tidy/fix pass over the paleoclimate-cluster notebooks (**T40–T45**),
done with Claude. T42 was deliberately left untouched (see *Known issues*).

> **Submitted as a pull request:** [EarthByte/GPlately-pyGMT-tutorials#1](https://github.com/EarthByte/GPlately-pyGMT-tutorials/pull/1),
> from branch `palaeoclimate-cluster-fixes` on the `amer7632` fork.

## Shared infrastructure

- **`Notebooks/paleoclimate_helpers.py`** — new module with three display-only
helpers used across the raster-map notebooks:
- `close_lon_seam` — cyclically pads a −180→180 grid so `grdimage` reaches the
±180° seam (kills the grey wedge at the antimeridian).
- `clamp_lat_poles` — extends the outermost latitude rows to ±90° before
interpolation (no NaN slivers at the poles).
- `refine_for_plot` — wraps the seam, clamps the poles, and bilinearly
resamples a coarse GCM field onto a fine (0.5°) grid so the Mollweide rim is
smooth instead of a staircase. **Display-only** — analysis still uses the
native grids. It ends with `.transpose("lat","lon")` because pyGMT infers the
plot region from dim order, and the intermediate steps can silently transpose.
T40, T41, T43, T44 import from it (via a `sys.path` insert of `Notebooks/`).

- **Data rename** — `data/scion_permian/` → `data/plasim_genie_permian/`, and the
three bundled files `scion_252Ma_*` → `plasim_genie_252Ma_*`. The bundle README
was rewritten (correct model name, Holden 2016 citation, T43/T44 cross-refs, and
a leaked local path removed).

- **`environment.yml`** — added `cmcrameri` + `openpyxl` (conda-forge) and
`cgeniepy` (pip). `bayfox` (T42) omitted; pySCION (T45) is a separate clone, not
a package.

## Model-naming correction (T40, T43, T44)

The bundled end-Permian climate fields were variously labelled "cGENIE-PLASIM"
and "SCION". They are **PLASIM-GENIE** runs (PLASIM atmosphere + GOLDSTEIN ocean;
the cGENIE biogeochemistry module was off), cited as Holden et al. (2016). All
three notebooks were renamed/relabelled accordingly, and their SCION references
were replaced. (T45 is genuinely pySCION-based, so SCION naming stays there.)

## Per-notebook changes

- **T40 — Deep-time snapshots.** Fixed the Mollweide rim/gridding artefacts
(seam + high-latitude staircase); a transposed Gaussian-lat grid that had
collapsed the deep-time map to a central blob; prose/code mismatches (palette
clamps); `cGENIE`→`GENIE`→`PLASIM-GENIE`; `jet`→`vik` anomaly palette; removed
SCION framing; reference fixes. Data caveat below.

- **T41 — cGENIE SST vs proxies.** Removed the unused/advertised `cgeniepy`
dependency (loads the bundled netCDF with xarray); fixed the rim artefact via
`refine_for_plot`; switched both panels to reconstructed 50 Ma coastlines
(were modern); corrected a `viridis`→`thermal` mismatch; fixed a wrong bundled
filename; stripped inline-URL clutter.

- **T43 — PLASIM-GENIE Permian SAT** *(renamed from `..._SCION_...`)*. SCION →
PLASIM-GENIE throughout; fixed wrong plate-model labels (prose said
Müller2022 / Zahirovic2022 for coastlines that are actually Merdith2021);
`refine_for_plot` on the SAT + land-sea-mask panels.

- **T44 — CO₂-sensitivity ensemble** *(renamed from `..._SCION_...`)*. Fixed the
SAT-map coastlines (were **modern** GSHHG on a 252 Ma map → now the model's own
land-sea-mask contour); fixed a mangled "CO₂" title (Unicode subscript doesn't
render in GMT); `refine_for_plot` rim fix; moved the spin-up / equilibrium
check up to §2 (validate before analysing); **fixed a 64× normalization bug** —
the bundled spin-up scalars were area-weighted but never divided by the number
of longitudes, so GAST and |TOA thermal| were 64× too large. SCION →
PLASIM-GENIE; broken version probe fixed.

- **T45 — pySCION Phanerozoic biogeochemistry.** Fixed a **years→Ma bug**:
`age` came from `state.time` (in years), so every time-series x-axis read
0–6×10⁸ "Ma" and, worse, SET 5's inflection lookup compared Ma against years —
all three paleo-Earth maps stamped the same ~349 ppmv. After `/1e6` the run
window reads "0–600 Ma" and the maps show age-correct pCO₂ (250 Ma → 8690,
30 Ma → 336 ppmv). Also adapted to the current `amer7632/pySCION` API
(`pySCION_initialise` now returns a `(run, interpstack, lipstack)` tuple), and
set the reference to Merdith et al. (2025, *Science Advances*).

## Known issues / follow-ups

- **T42 (bayfox SST)** — left untouched. It won't run with `bayfox` installed:
the code calls a non-existent `bayfox.predict_sst` (real API is
`predict_seatemp`, returning a `Prediction`); `SPECIES="G_ruber"` is invalid
and unused (valid code is `"G. ruber"` via `foram=`). The bundled CSV is now a
planktonic WPWP (ODP 806) record, but the prose/Data-availability still describe
the old LR04 benthic stack and `CORE_LON_LAT` points at the North Atlantic.

- **`data/paleoclimate/hadcm3_modern_mat.nc`** (T40 modern panel) is nearly
zonal — no land-sea contrast and far too warm at the poles — i.e. not a
faithful HadCM3 field. Left as-is; worth replacing at source.

- **`plasim_genie_252Ma_spinup.nc`** still stores the 64×-inflated GAST/thermal
values at source; T44 divides by `n_lon` as a workaround.

- **pySCION** is not bundled: T45 needs a clone at `external/pySCION`
(`https://github.com/amer7632/pySCION`, gitignored) plus `openpyxl`.
45 changes: 45 additions & 0 deletions data/plasim_genie_permian/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# PLASIM-GENIE end-Permian (252 Ma) CO2-sensitivity ensemble — compact bundle

Source: Andrew Merdith's PLASIM-GENIE coupled-model output, version `MER25rev6`
(2025-08-01). PLASIM-GENIE = PLASIM atmosphere + GOLDSTEIN ocean (the cGENIE
biogeochemistry module was off for these runs). PLASIM atmosphere is T21
(64 lon x 32 lat, ~5.625 deg). GOLDSTEIN ocean is on a 64 lon x 32 lat C-grid
with 16 depth levels (only the surface and 2D bathymetry are bundled here).

## Files

- `plasim_genie_252Ma_equilibrium.nc` — last-year (year 3000) snapshot per CO2
level. Dims (co2_ppm: 10, latitude: 32, longitude: 64). Variables: SAT,
surface_T, sea_ice_cover, sea_ice_thickness, snow_depth, surface_albedo,
landsea_mask, orography, top_thermal, top_solar.
- `plasim_genie_252Ma_bathymetry.nc` — GOLDSTEIN bathymetry per CO2. Identical
across CO2 levels (same input geography); bundled per-CO2 for symmetry.
- `plasim_genie_252Ma_spinup.nc` — (co2_ppm, year) energy-balance time series
for the model-stability / spin-up curves (T44 §5). 30 yearly steps per CO2
level (years 100..3000 in steps of 100).

## CO2 levels

140, 280, 560, 1120, 2240, 3360, 4480, 8960, 17920, 35840 ppm.
End-Permian best-estimate ~1120 ppm; runaway scenarios up to 35840 ppm.

## Size

Full bundle ~50 KB compressed (vs 1.5 GB for the raw model output).
That covers everything T43 + T44 need.

## Provenance

For the full model output (every yearly snapshot, every variable), contact
Andrew Merdith.

## Citation

Cite the PLASIM-GENIE model description:

Holden, P.B., Edwards, N.R., Fraedrich, K., Kirk, E., Lunkeit, F. & Zhu, X.
(2016). PLASIM-GENIE v1.0: a new intermediate complexity AOGCM.
*Geoscientific Model Development* 9, 3347-3361.

And acknowledge Andrew Merdith's 252 Ma ensemble as personal communication
pending publication.
46 changes: 0 additions & 46 deletions data/scion_permian/README.md

This file was deleted.

4 changes: 4 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ dependencies:
- pyarrow # for parquet caches (T10)
- imageio # for T03 animations
- ffmpeg # for T03 MP4 encoding
- cmcrameri # Crameri scientific colour maps (T44, T45)
- openpyxl # Excel forcings for pySCION (T45)
- pip
- pip:
- cgeniepy # cGENIE output reader (T41); pip-only, not on conda-forge