Skip to content

feat: add hyper-xtb NN-xTB back-end (hyper-crest)#1

Open
UMI5751 wants to merge 2 commits intomasterfrom
feat/hyperxtb-nnxtb
Open

feat: add hyper-xtb NN-xTB back-end (hyper-crest)#1
UMI5751 wants to merge 2 commits intomasterfrom
feat/hyperxtb-nnxtb

Conversation

@UMI5751
Copy link
Copy Markdown
Collaborator

@UMI5751 UMI5751 commented Apr 17, 2026

Summary

Adds nnxtb as a first-class CREST back-end, powered by the hyper-xtb NN-xTB
C API (hxtb_nnxtb_*) landing in talo/hyper-xtb#2.
Goal: CREST-speed conformer sampling at DFT-level accuracy by swapping the
underlying GFN2-xTB engine for the MACE-corrected one in hyper-xtb.

The MACE weights are loaded exactly once per `crest` invocation — a
persistent `HxtbNNxtbEngine*` lives on `calculation_settings%hyperxtb`
for the duration of the sampling run and is reused by every conformer
energy/gradient call, matching the NN-xTB deployment constraint.

What's in here

Architecturally this mirrors the existing `tblite_api` plumbing so the rest
of CREST (ancopt, metadynamics, CREGEN, ensemble ops) treats it as just
another engine — no downstream changes needed.

  • `src/calculator/hyperxtb_api.F90` (new). `hyperxtb_data` owns the C
    engine handle and MACE model path. `hyperxtb_setup` calls
    `hxtb_nnxtb_init` (loads MACE weights). `hyperxtb_singlepoint` flattens
    `coord%xyz`/`coord%at` into the C API's Z/xyz arrays, calls
    `hxtb_nnxtb_compute`, unflattens the gradient. `hyperxtb_cleanup` frees
    the engine. All wrapped in `#ifdef WITH_HYPERXTB` so the stub compiles
    without the hyper-xtb headers.
  • `src/calculator/api_engrad.f90`: `hyperxtb_engrad` — same loadnew / OMP
    critical pattern as `tblite_engrad`.
  • `src/calculator/calculator.F90`: new `jobtype%hyperxtb` case.
  • `src/calculator/calc_type.f90`: enum entry, jobdescription row,
    `hyperxtb` / `hyperxtb_model` fields on `calculation_settings`,
    deallocate paths (cleans up C engine before freeing), and
    `create_calclevel_shortcut` aliases.
  • `src/parsing/parse_calcdata.f90`: `method = "nnxtb"` (or `hyperxtb`)
    and `mace_model = "/path/to/model.mxtb"` in TOML input.
  • `CMakeLists.txt` + `config/CMakeLists.txt`: `WITH_HYPERXTB` option
    (default OFF). When ON, requires `-DHYPERXTB_ROOT` and
    `-DHYPER_MACE_ROOT` pointing at built trees; builds an
    `hyperxtb::hyperxtb` INTERFACE target carrying include + libs.
  • `docs/hyper_crest.md`: build recipe + TOML example.

Test plan

  • `cmake -B build` without `WITH_HYPERXTB` configures and keeps the
    default CREST build identical (byte-level, for anything except the
    new srcs list) — the new `.F90` compiles its no-`#ifdef` stub path.
  • `cpp -DWITH_HYPERXTB hyperxtb_api.F90` expands cleanly (3
    `bind(C, name=...)` interfaces present, no warnings).
  • End-to-end build `-DWITH_HYPERXTB=ON -DHYPERXTB_ROOT=... -DHYPER_MACE_ROOT=...`
    against hyper-xtb#2 + hyper-mace xtb-implementation — target for
    CI once we have a runner with GPUs + a .mxtb model file.
  • `crest input.toml` with `method = "nnxtb"` on a small molecule,
    compared to standard `method = "gfn2"` — happens in the follow-up
    benchmark PR (DFT rescore of top conformers from both).

Dependencies

This PR only compiles in the NN-xTB path when paired with:

Base branch is upstream `master`. We can rebase onto a hyper-crest main
branch if we cut one from `master`, but since this repo was just forked
for the NN-xTB work the two are identical right now.

Validation (RunPod A6000, 2026-04-18)

All four checklist items green. Built hyper-mace (xtb-implementation, PIC) + hyper-xtb (feat/nnxtb-capi, WITH_NNXTB=ON) + this branch (WITH_HYPERXTB=ON) end-to-end. crest input.toml with method = "nnxtb" on water:

 TOTAL ENERGY          -5.1179085996 Eh
 GRADIENT NORM          0.0017862478 Eh/a0
 CREST terminated normally.

Plumbing confirmed via nm (5 new symbols resolved: hxtb_nnxtb_{init,compute,free} + __hyperxtb_api_MOD_hyperxtb_{setup,singlepoint}) and a fault-injection run with a bogus .mxtb that produced a clean error trace all the way from mace_xtb_load back up to crest_main. Full benchmark numbers in PR #2.

New back-end `nnxtb` calls hyper-xtb's hxtb_nnxtb_* C API (persistent
engine = MACE weights loaded once per crest invocation) via a Fortran
iso_c_binding wrapper. Architecture mirrors the existing tblite_api
plumbing so the rest of CREST (optimizer, metadynamics, CREGEN, etc.)
sees it as just another engine.

- src/calculator/hyperxtb_api.F90: new module. hyperxtb_data owns the
  HxtbNNxtbEngine* and the MACE model path. hyperxtb_setup is called
  on the first energy/gradient request; hyperxtb_singlepoint is the
  per-frame dispatch (flattens mol%xyz/at → zs+xyz buffers, calls
  hxtb_nnxtb_compute, unflattens grad).
- src/calculator/api_engrad.f90: hyperxtb_engrad — same loadnew/OMP
  critical pattern as tblite_engrad.
- src/calculator/calculator.F90: new jobtype%hyperxtb case in the
  per-calculation dispatch switch.
- src/calculator/calc_type.f90: jobtype enum + jobdescription entry,
  hyperxtb / hyperxtb_model fields on calculation_settings, deallocate
  paths, and create_calclevel_shortcut aliases (nnxtb, hyperxtb).
- src/parsing/parse_calcdata.f90: method=nnxtb|hyperxtb accepted in
  TOML; mace_model/hyperxtb_model/nnxtb_model key sets the .mxtb path.
- CMakeLists.txt + config/CMakeLists.txt: WITH_HYPERXTB option (default
  OFF). When ON, requires -DHYPERXTB_ROOT and -DHYPER_MACE_ROOT; builds
  an hyperxtb::hyperxtb INTERFACE target carrying include/libs and
  defines WITH_HYPERXTB so the Fortran source compiles with the real
  bindings (stub error-stops otherwise).
- docs/hyper_crest.md: build + usage walkthrough.

Default build (WITH_HYPERXTB=OFF) is unchanged.
Downstream libcrest (static + shared) and the crest-exe target were
failing to link with undefined references to cudaMalloc / cudaFree /
cudaStreamCreate / mace_xtb_compute_cuda when hyper-xtb was built
against a CUDA hyper-mace, because libhyperxtb_core.a now carries GPU
code from the new src/nnxtb_capi_cuda.cu TU (via
hyper-xtb#2/feat/nnxtb-capi). Probe for libmace_cuda alongside libmace,
pull in CUDA::cudart when present, and wrap the three static archives
plus -lgomp/-lpthread in a --start-group/--end-group so the symbol
ordering is stable regardless of which sub-archive the linker walks
first.
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