Skip to content

REFAC: replace in-memory Python–C bindings with grt CLI workflow - #287

Merged
Dengda98 merged 4 commits into
mainfrom
refactor-python-cli-workflow
Aug 12, 2026
Merged

REFAC: replace in-memory Python–C bindings with grt CLI workflow#287
Dengda98 merged 4 commits into
mainfrom
refactor-python-cli-workflow

Conversation

@Dengda98

Copy link
Copy Markdown
Owner

Summary

This PR changes the main Python computation path from “ctypes / in-memory C bindings” to “assemble arguments → run the grt executable → read/write SAC / NetCDF results”. The goal is to let Python and the command-line interface share one computation entry point, and to reduce the cost of future maintenance and feature work.

Commits included:

  • REFAC: route Python API through grt CLI instead of in-memory bindings
  • TEST: update comparison and smoke tests for CLI-based Python workflow
  • DOC: sync tutorials, examples and install guide with CLI-based Python API

Motivation

Previously, Python loaded libgrt.so through ctypes and relied on a parallel set of in-memory wrappers (for example c_structures, pygrn, and a large memory-oriented C API) for Green’s functions, synthesis, and tensor calculations. That design had several drawbacks:

  1. Dual maintenance: the same feature had to be maintained both as a grt CLI module and as a nearly parallel Python–C memory interface, which was costly and easy to drift.
  2. Heavy coupling: Python had to understand many C struct layouts and internal states, which made later C-core refactors harder.
  3. Inconsistent result model: the CLI naturally wrote files (SAC / NetCDF), while Python mainly returned in-memory objects. Docs, tests, and user scripts needed two narratives.

After routing Python through grt:

  • computation logic stays in the C executable modules;
  • Python focuses on argument checks, path setup, command assembly, and reading results back;
  • documentation examples, tests, and daily CLI usage follow the same path, so new features can usually extend grt first.

What changed

1. New CLI helper layer

Added pygrt/cli.py:

  • find_grt(): prefer the bundled pygrt/C_extension/bin/grt, then fall back to PATH
  • run_grt(...): run grt via subprocess; when print_log=True, stream logs live; when False, capture output and attach it to errors on failure
  • format_float / format_range: normalize numeric CLI formatting

2. Refactored public Python API

pygrt/pymod.py and pygrt/utils.py now assemble CLI arguments and call run_grt:

  • dynamic / static Green’s functions: compute_grn / compute_static_grn
  • dynamic / static synthesis: compute_syn / compute_static_syn
  • strain / rotation / stress: compute_strain / compute_rotation / compute_stress

Typical workflow:

  1. Build a model with PyModel1D(modelpath)
  2. Set output locations with set_dynamic_grn_path(...) / set_static_grn_path(...)
  3. Call compute_* to run the corresponding grt module and write files
  4. Read results back with ObsPy / read_static_nc, or pass return_result=True

3. Removed the old in-memory path

Removed wrappers and exports that are no longer needed, including:

  • deleted pygrt/pygrn.py
  • deleted pygrt/c_structures.py
  • reduced old memory-workflow exports from c_interfaces.py / __init__.py

A few capabilities that still fit library calls remain on the ctypes path (for example travel times, time functions, and the Lamb problem), but main computations no longer depend on in-memory bindings.

4. Tests and docs updated

  • Added / updated C–Python argument-mapping and end-to-end comparison tests (including test_cli_args.py)
  • Synced Tutorial / Gallery / Advanced / install.rst / intro.rst to the “call grt + write files + read back” model
  • Fixed several example-parameter mismatches and C/Python script inconsistencies

Final user-facing form

Users still work through high-level APIs such as PyModel1D, but the semantics are now:

Python organizes paths and arguments; the real computation is done by the bundled (or PATH-resolved) grt executable; results are file-centered.

Prebuilt packages ship a platform-specific grt. For Python-only use, find_grt() locates the bundled executable automatically, so no extra PATH setup is required. Configure PATH only if you also want to invoke grt ... directly in a terminal.

When reading dynamic tensor results back, distinguish them by filename prefix:

  • strain_*.sac
  • rotation_*.sac
  • stress_*.sac

SAC headers do not record whether a file is strain / rotation / stress; channel names only carry component labels such as ZZ or NE.

Before / After

Architecture

Item Before After
Main Python compute entry ctypes calls into libgrt.so / memory APIs subprocess runs the grt executable
Argument passing Python structs / C function arguments assembled grt <module> [options]
Result model mostly in-memory objects (e.g. Stream / custom objects) write SAC / NetCDF by default; optional return_result=True
Logging old print path print_log controls whether intermediate grt output is shown
Relation to CLI two entry points, easy to diverge Python reuses the CLI path
Python-only install depended on dynamic-library binding details depends on bundled grt; prebuilt packages work after install
Future extension often required changing C API + Python bindings + CLI together prefer extending grt modules; Python mainly forwards arguments

Dynamic Green’s functions (illustrative)

Before (illustrative) After
Setup pymod = PyModel1D("milrow") pymod = PyModel1D("milrow")
Output location mostly carried by return values / internal objects pymod.set_dynamic_grn_path("GRN")
Compute st_list = pymod.compute_grn(...) (in-memory return) pymod.compute_grn(...) (writes under GRN/...)
Read back use returned Stream / list directly from obspy import read
st = read("GRN/*/*.sac")
Equivalent CLI a separate grt greenfn ... command Python calls the same class of grt greenfn ... internally

Synthesis / tensors (illustrative)

Step Before (illustrative) After
Synthesize displacement in-memory synthesis from GF objects, or dedicated helpers pymod.compute_syn(..., output_path="syn_dc", ...)
Read displacement returned Stream read("syn_dc/?.sac"), or return_result=True
Strain / rotation / stress in-memory postprocessing / old helpers pygrt.utils.compute_strain("syn_dc", return_result=True)
Tensor separation easy to mix with displacement channels read only strain_*.sac / rotation_*.sac / stress_*.sac

Module cleanup

Item Before After
pygrt.cli absent (or not a public helper) public helpers: find_grt / run_grt, etc.
pygrt.pygrn present removed
pygrt.c_structures present removed
Python main path vs libgrt.so strongly required not required for main compute; a few helpers may still use the library

Breaking changes / migration notes

This is a breaking change. Old “in-memory binding / list-of-Stream GF return” code must be migrated.

Suggested migration steps:

  1. Call set_dynamic_grn_path / set_static_grn_path before computation
  2. Replace return-value-driven code with file reads, or pass return_result=True
  3. For dynamic tensors, read prefixed filenames; do not blindly read("*.sac")
  4. Ensure pygrt/C_extension/bin/grt exists after install (included in prebuilt packages)

Minimal example:

import pygrt
from obspy import read

pymod = pygrt.PyModel1D("milrow")
pymod.set_dynamic_grn_path("GRN")
pymod.compute_grn(
    depsrc=2.0, deprcv=0.0, distarr=[10.0],
    nt=500, dt=0.02,
)
stgrn = read("GRN/*/*.sac")

pymod.compute_syn(
    dist=10.0, azimuth=30.0, scale=1e24,
    output_path="syn_dc", source="DC",
    strike=33, dip=50, rake=120,
)
stsyn = read("syn_dc/?.sac")

Dengda98 and others added 4 commits August 12, 2026 12:23
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
… API

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@Dengda98
Dengda98 merged commit de65478 into main Aug 12, 2026
8 of 9 checks passed
@Dengda98
Dengda98 deleted the refactor-python-cli-workflow branch August 12, 2026 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant