Port simple_paleobathymetry (Steps 1-4) and its distance/sediment engine into gplately - #449
Port simple_paleobathymetry (Steps 1-4) and its distance/sediment engine into gplately#449michaelchin wants to merge 12 commits into
Conversation
Adds gplately.paleobathymetry with age_to_basement_depth() (Step 1: four published thermal-subsidence models), dutkiewicz_2017_sediment_thickness() (Step 3: the Dutkiewicz et al. 2017 age/distance-to-margin polynomial fit), and sediment_isostatic_correction()/paleobathymetry() (Step 4: Sykes 1996 isostatic sediment-load correction), ported from and numerically verified against EarthByte's simple_paleobathymetry workflow. Ships the RHCW18 age-depth lookup table used by the 'rhcw18' model. Step 2 (lifetime-mean distance to the nearest passive continental margin) and Step 5 (pyBacktrack merge) are deliberately not included here: Step 2 needs the proximity/obstacle-routing engine tracked in gplately#445, and Step 5 is tracked in gplately#447. See the module docstring and the #444 comment thread for the reasoning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…; wire into #444 Adds gplately.sediment_thickness, a port of EarthByte's predicting-sediment-thickness ocean_basin_proximity.py engine: - generate_distance_grids(): for each ocean point in an age grid, reconstruct it backward through time (topological reconstruction via pygplates.TopologicalModel, distance queries via gplately's own ptt.utils.proximity_query) and accumulate its lifetime-mean distance to the nearest proximity feature (e.g. passive-margin COB line segments). - generate_sediment_thickness_grids(): combines an age grid with the distance grid above via gplately.paleobathymetry.dutkiewicz_2017_sediment_thickness. Exposed on the CLI as 'gplately generate-distance-grids'/'generate-sediment-grids' (aliases gdg/gsg), per #445's stated scope. Not ported: continent-obstacle shortest-path routing (ocean_basin_proximity.py's shortest_path.py) - distances are currently always great-circle; and topological (as opposed to static) proximity features. Both are documented gaps in the module docstring. Validated end-to-end against a real plate model (rotations/topologies/COBs) with a downloaded age grid: produces physically sensible distance (0-3000 km) and paleobathymetry (-2 to -5.5 km) values. Added unit tests using the existing Muller2019 test fixtures plus a synthetic age grid. Also adds gplately.paleobathymetry.simple_paleobathymetry(), the Steps 1-4 orchestrator for #444, which now calls generate_distance_grids() for Step 2 instead of requiring the caller to supply a distance grid. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Groups them with the other gridding code (oceans.py, topology/isochron seafloor grids) per gplately's module layout. Public API is unaffected - everything stays re-exported from gplately/__init__.py (gplately.paleobathymetry, gplately.simple_paleobathymetry, gplately.generate_distance_grids, etc.). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wraps gplately.grids.paleobathymetry.simple_paleobathymetry() as a new CLI subcommand (alias 'pb'), completing the CLI surface alongside the existing 'generate-distance-grids'/'generate-sediment-grids' (#445): previously there was no CLI path to Steps 1/4, or to running the full pipeline in one command, even though that one-command workflow is the actual UX simple_paleobathymetry (#444) is modelled on. Refactored gplately/commands/sediment_thickness.py to extract _add_distance_arguments()/_resolve_rotation_topology_proximity_files(), now shared between 'generate-distance-grids' and the new 'paleobathymetry' command instead of being duplicated. Verified end-to-end against a real plate model (muller2025) via the CLI; also added the same real invocation to tests-dir/test-cli.sh. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Added a `gplately paleobathymetry` CLI command (alias `pb`), wrapping `simple_paleobathymetry()` to run Steps 1-4 end to end in one command - previously there was CLI coverage for Steps 2/3 only (`generate-distance-grids`/`generate-sediment-grids`), with no CLI path to the full pipeline even though that's the actual one-command UX the source `simple_paleobathymetry` workflow offers. Refactored the shared argparse/file-resolution logic out of `commands/sediment_thickness.py` so it's reused by the new command rather than duplicated. Verified end-to-end against a real plate model via the CLI, and added the same invocation to `tests-dir/test-cli.sh`. |
…r in test-cli.sh The combined 'paleobathymetry' command already exercised generate_distance_grids()/ generate_sediment_thickness_grids() at the Python level, but not the CLI-level handoff between running 'gdg' and 'gsg' as two separate invocations (writing to --distance-grids-dir, then a second process reading those grids back off disk) - a distinct code path in commands/sediment_thickness.py that wasn't covered by either the unit tests or test-cli.sh. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds gplately.grids.pybacktrack_paleobathymetry.merge_pybacktrack_paleobathymetry(), a thin wrapper around pybacktrack.reconstruct_paleo_bathymetry_grids()'s merge support: computes pyBacktrack's present-day paleobathymetry and merges in Steps 1-4's grids to also cover submerged continental crust and crust that has since subducted. pybacktrack is an optional dependency (new gplately[paleobathymetry] extra), imported lazily so `import gplately` never requires it. Wired into gplately.grids.paleobathymetry.simple_paleobathymetry() as an opt-in final step (pybacktrack=True, requires output_directory, static_polygon_filename, present_day_age_grid_filename - Step 5 merges the Step 4 grids by reading them back off disk), and into the 'gplately paleobathymetry' CLI command as --pybacktrack (--static-polygons and --present-day-age-grid are auto-resolved from -m/--model when not given explicitly). Validated with the actual pybacktrack package (now installed locally): merging pyBacktrack's output with a real Steps-1-4 run (muller2025) via both the Python API and the CLI increases finite-cell coverage from 330 to 441 out of 703 grid points, as expected (pyBacktrack fills in the submerged-continental-crust/subducted-crust gaps Steps 1-4 leave as NaN). Unit tests added (skipped when pybacktrack isn't installed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Added Step 5 (pyBacktrack merge, #447) too, so this PR now covers #444, #445 and #447.
Only thing still open for the whole paleobathymetry effort now is #446 (continent-contouring passive-margins use case) and the continent-obstacle routing gap noted in #445's module docstring. |
Adds gplately.grids.continent_contouring, a port of EarthByte's continent-contouring create_passive_margins.py (the one use case that repository contains), built entirely on gplately's own already-vendored continent-contouring engine (gplately.ptt.continent_contours.ContinentContouring): - passive_margin_polylines(): split one continent-contour polyline into its passive-margin segments, by removing the parts close to a subduction zone (the core algorithm). - generate_passive_margins(): the full driver - contour continents through time and split each contour, returning/writing the aggregated contour and passive-margin feature collections plus per-time continental-crust masks. Exposed on the CLI as 'gplately generate-passive-margins' (alias gpm). generate_passive_margins()'s output composes directly with #445's generate_distance_grids() as an alternative to a static COB line-segment file (its passive_margin_features is already the pygplates.FeaturesFunctionArgument-compatible type that proximity_features expects) - validated end-to-end against a real plate model (muller2025), no glue code needed. Noted in sediment_thickness.py's module docstring. Validated with real data throughout: contouring, splitting, CLI, and the composition with generate_distance_grids all run correctly against muller2025. Unit tests added, including hand-checked geometric cases for the segment-splitting algorithm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Added #446 too (continent-contouring passive margins), so this PR now covers #444, #445, #446 and #447 - the whole paleobathymetry tracking effort (#448) except the continent-obstacle routing gap noted in #445's docstring.
|
Adds gplately.lib.shortest_path, a faithful port of predicting-sediment-thickness's shortest_path.py: a spherical grid (Grid/ObstacleGrid/DistanceGrid) that computes shortest-path distances *around* obstacle geometries via Dijkstra's algorithm, instead of a great-circle straight line that can cut through land. Wires this into generate_distance_grids() as new optional arguments (continent_obstacle_features, plate_boundary_obstacle_feature_types, shortest_path_grid_subdivision_depth) - when continent_obstacle_features is given, each time step builds an obstacle grid from the reconstructed obstacles (plus, by default, mid-ocean ridge and subduction zone plate boundary sections) and routes distances around it instead of using plain great-circle distance. Propagated through to gplately.grids.paleobathymetry.simple_paleobathymetry() and the CLI (--route-around-continents / --continent-obstacles / --shortest-path-grid-depth, on both 'generate-distance-grids' and 'paleobathymetry'). Validated: a standalone synthetic-obstacle test confirms routed distance is strictly longer than great-circle when a straight line would cut through the obstacle, and matches great-circle (within grid-discretisation noise) on a clear path. Against a real plate model (muller2025) with real coastlines, obstacle-routed distances average ~70-80km longer than great-circle across a coarse global test grid, with a small number of individual-point exceptions consistent with the algorithm's known grid-interpolation smoothing (shrinks with finer grid resolution, as expected). Also verified end-to-end via the CLI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Added continent-obstacle routing too, closing the one remaining documented gap from #445.
With this, the only thing left from the original scope (per #448) is #446... which is also already in this PR. So as far as I can tell, everything from the original email thread is now implemented, at least at a first-pass level of completeness. |
Combines test_9_paleobathymetry.py, test_10_sediment_thickness.py,
test_11_pybacktrack_paleobathymetry.py, test_12_continent_contouring.py and
test_13_shortest_path.py into a single test_9_paleobathymetry.py, organised
into labelled sections by submodule. Renamed the three colliding
test_public_api_exports() functions (one per merged file) so all three
survive rather than the later definitions silently shadowing earlier ones.
Replaced the module-level pytest.importorskip("pybacktrack") with a
try/except + skipif marker scoped to just the two pybacktrack tests, so a
missing optional pybacktrack dependency no longer skips the whole file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- gplately/grids/paleobathymetry.py:
- simple_paleobathymetry() no longer crashes with "got multiple values for
keyword argument 'max_distance_km'" when sediment_thickness_kwargs
explicitly overrides it (a documented, valid override) - the caller's
value now correctly wins instead of colliding with the internally
injected clamp_distance_km.
- age_to_basement_depth(model="rhcw18") no longer maps tiny negative ages
(floating-point/grid-interpolation noise right at a ridge) to 0 m (sea
level); they're now clamped to 0 before interpolation, giving the
table's actual ridge-crest depth (~-2500 m), consistent with how gdh1/
parsons_sclater already special-case age < 0.
- simple_paleobathymetry(pybacktrack=True) now raises a clear TypeError if
rotation_model is an already-constructed pygplates.RotationModel (its
own docstring says that's an accepted rotation_model input in general,
but pyBacktrack specifically needs raw filenames) instead of failing
obscurely inside pybacktrack.
- gplately/grids/continent_contouring.py: passive_margin_polylines() no
longer splits a passive-margin stretch that happens to straddle a closed
contour ring's arbitrary start/end point into two separate output
polylines - the ring is now rotated to start right after an active edge
(if any) before splitting, so the array boundary never falls inside a
passive stretch.
- gplately/commands/sediment_thickness.py, .../continent_contouring.py:
fractional --time-step/--min-time/--max-time (all declared type=float on
the CLI) were silently truncated via int() before building the time
range - e.g. --time-step 0.5 crashed with "range() arg 3 must not be
zero", and --time-step 2.5 silently became 2. Replaced with a proper
float-aware _time_range() helper (shared between the two command
modules). Also added the missing return_none_if_not_exist=True on
Topologies layer lookups (a model without that layer now hits the
intended "No rotation/topology files found" message instead of an opaque
plate-model-manager exception), and continent_contouring.py now merges in
a model's Cratons layer when present, matching seafloor_grids.py's
existing continent-file resolution.
- gplately/commands/paleobathymetry.py: --pybacktrack no longer silently
drops all but the first file when a plate model's StaticPolygons layer
resolves to multiple files (pyBacktrack's static_polygon_filename takes
exactly one file) - they're now merged into one temporary file instead.
- tests-dir/test-cli.sh: fixed a reference to unittest/test_seafloor_gridding.sh
(underscores); the actual script is test-seafloor-gridding.sh (hyphens),
so the CLI smoke test would have aborted there.
Added regression tests for the three algorithm/API-level fixes (RHCW18
ridge depth, closed-ring seam merge, max_distance_km override). Full test
suite (33 tests) still passes; CLI commands manually re-verified end to end,
including the previously-crashing fractional --time-step case.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Ran a thorough multi-angle code review over the whole diff before asking for review. Fixed everything that was a genuine bug:
Added regression tests for the algorithm/API-level fixes; full suite (33 tests) passes, and I re-ran the CLI commands manually including the previously-crashing fractional `--time-step` case. Deliberately not fixed, flagged for awareness rather than action in this PR:
I think this is ready for human review now. |
Adds paleobathymetry (pb), generate-distance-grids (gdg), generate-sediment-grids (gsg) and generate-passive-margins (gpm) to the CLI docs, following the existing per-subcommand section format (description + usage examples). Verified with a clean sphinx-build (no new warnings/errors, sections render correctly). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Most of the work in the PR was done by Claude Code. |
|
Wow, this is quite some work - nice! I'll take a look - it might be a while though. |
Summary
Implements #444 and #445 together (they turned out to be tightly coupled - see the discussion on #444).
gplately.paleobathymetry(#444) - Steps 1, 3, 4 of EarthByte's simple_paleobathymetry workflow, plus an orchestrator:age_to_basement_depth()- Step 1: seafloor age -> basement depth (gdh1/rhcw18/parsons_sclater/crosby09). Ships the RHCW18 lookup table.dutkiewicz_2017_sediment_thickness()- Step 3: Dutkiewicz et al. (2017) sediment-thickness polynomial fit.sediment_isostatic_correction()/paleobathymetry()- Step 4: Sykes (1996) isostatic correction, combined into final paleobathymetry.simple_paleobathymetry()- runs Steps 1-4 end to end, calling intogplately.sediment_thicknessfor Step 2.gplately.sediment_thickness(#445) - a port ofpredicting-sediment-thickness'socean_basin_proximity.pyengine:generate_distance_grids()- Step 2: reconstructs each ocean point backward through time and computes its lifetime-mean distance to the nearest proximity feature (e.g. passive-margin COB line segments), usingpygplates.TopologicalModeland gplately's ownptt.utils.proximity_query.generate_sediment_thickness_grids()- Step 3's grid-level driver, composing the above withdutkiewicz_2017_sediment_thickness().gplately generate-distance-grids/generate-sediment-grids(aliasesgdg/gsg).Validation
simple_paleobathymetry()pipeline run end-to-end against a real plate model (Müller et al. 2025, via the Plate Model Manager) with a downloaded age grid: produces physically sensible distance-to-margin (0-3000 km) and paleobathymetry (-2 to -5.5 km) values.generate-distance-grids->generate-sediment-grids) smoke-tested end-to-end against the same real data.test_9_paleobathymetry.py,test_10_sediment_thickness.py), using the existing Muller2019 test fixtures.Deliberately out of scope
shortest_path.py) - distances are currently always great-circle. This is a real difference fromsimple_paleobathymetry's default config (use_continent_obstacles: true).All flagged in the relevant module docstrings.
Test plan
python -m pytest -vv tests-dir/pytestcases/test_9_paleobathymetry.py tests-dir/pytestcases/test_10_sediment_thickness.py tests-dir/pytestcases/test_0_imports.py- all passing.black --checkclean on all changed/added files.pytest -vv tests-dir/pytestcases(not run here - some fixtures need network access / take a while).🤖 Generated with Claude Code