Native Fortran backend (HASH-HP)
CNCHASH is built around a Modern Fortran + OpenMP backend. All numerical
work (grid search, S/P amplitudes, uncertainty, velocity tables) runs in
libhashhp; the Python layer provides the API, file handling, and batch
dispatch. Without the compiled library the package raises a clear build
error.
Architecture
HASH_complete/ # original Fortran HASH v1.2 (immutable upstream)
cnchash/ # Python package
├── backend/
│ ├── base.py # HashBackend contract
│ └── fortran_backend.py # ctypes binding to libhashhp
├── driver.py # run_hash / run_hash_batch
├── io.py # phase/station file handling
└── utils.py # pure-Python helpers
src/hash_hp/ # Modern Fortran core
├── hash_kinds.f90 # real64/int32 kinds
├── hash_geometry.f90 # TO_CAR/FPCOOR/cross
├── hash_rotation.f90 # cached rotation grid
├── hash_runtime.f90 # grid cache + OpenMP thread control
├── hash_focalmc.f90 # FOCALMC, OpenMP over rotations
├── hash_misfit.f90 # GET_GAP/GET_MISF
├── hash_uncertainty.f90# MECH_ROT/MECH_AVG/MECH_PROB
├── hash_amplitude.f90 # FOCALAMP_MC/GET_MISF_AMP
├── hash_velocity.f90 # LAYERTRACE/MK_TABLE/GET_TTS
├── hash_batch.f90 # full event pipeline + event-parallel batch
└── hash_c_api.f90 # stable ISO_C_BINDING ABI (ctypes, no f2py)
Design rules
No I/O in the kernel.
src/hash_hpreceives arrays and returns arrays; Python handles files, formats, and exceptions.No allocations in hot loops. Per-event workspaces are reused; the rotation grid is built once per
dangand shared read-only.No nested OpenMP. Batch mode parallelizes over events or over rotations, never both.
Deterministic by default. Acceptable-mechanism selection takes the first
maxoutin grid order (selection=0). Passselection=1to reproduce the original HASH random selection.No
-ffast-mathby default. Scientific correctness first; setCNCHASH_FAST_MATH=ONexplicitly.HASH_complete/is untouched. It is the golden reference.
Building
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
Portable binaries are the default. Set -DCNCHASH_NATIVE_MARCH=ON for
a machine-specific build that is roughly 2.5x faster on the machine it
was compiled for.
Backend selection
from cnchash import run_hash, available_backends, get_backend_info
run_hash(az, the, pol, qual, backend="auto") # default (fortran)
run_hash(az, the, pol, qual, backend="fortran") # explicit
print(available_backends())
print(get_backend_info())
Environment variables: CNCHASH_BACKEND, CNCHASH_NUM_THREADS,
CNCHASH_HASHHP_LIB (explicit library path).
Batch mode
run_hash_batch sends many events to the Fortran core in one call
(CSR-style flat arrays), with event-level OpenMP parallelism for large
catalogs:
from cnchash import run_hash_batch
results = run_hash_batch(events, nmc=30, backend="fortran", num_threads=16)
Deliberate differences from HASH v1.2
Item |
HASH v1.2 |
HASH-HP |
|---|---|---|
GET_GAP gaps |
INTEGER (implicit typing truncates) |
REAL (gaps are continuous) |
GET_MISF fault vectors |
radians misread as degrees (unit bug) |
fixed: degrees passed to FPCOOR |
GET_MISF_AMP stdr |
weights summed for amplitude stations |
identical |
selection when nf > maxout |
|
deterministic default |
velocity GET_TTS depth check |
|
|
velocity ray buffers |
fixed 10001 (can overflow) |
20001 heap-allocated |
All deviations are bug fixes. Tests pin the acceptable-mechanism sets and preferred solutions to the original HASH v1.2 on identical inputs (see tests/test_accuracy.py), with the only remaining differences being single-vs-double-precision effects at cluster boundaries.