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
73 changes: 73 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,76 @@ if(NOT TARGET "tblite::tblite" AND WITH_TBLITE)
add_compile_definitions(WITH_TBLITE)
endif()

# hyper-xtb + hyper-mace (NN-xTB backend).
# This is the new calculator back-end added by hyper-crest. HYPERXTB_ROOT
# must point at a built hyper-xtb tree (feat/nnxtb-capi branch or later)
# configured with -DHYPERXTB_WITH_NNXTB=ON. HYPER_MACE_ROOT is the built
# hyper-mace tree (xtb-implementation branch) exporting libmace.a.
#
# These are carried as a small interface target so the object/static/shared
# downstream targets can link against them uniformly.
if(WITH_HYPERXTB)
if(NOT HYPERXTB_ROOT)
message(FATAL_ERROR
"WITH_HYPERXTB=ON requires -DHYPERXTB_ROOT=/path/to/hyper-xtb (built with -DHYPERXTB_WITH_NNXTB=ON)")
endif()
if(NOT HYPER_MACE_ROOT)
message(FATAL_ERROR
"WITH_HYPERXTB=ON requires -DHYPER_MACE_ROOT=/path/to/hyper-mace (built libmace.a)")
endif()
find_library(HYPERXTB_LIB hyperxtb_core
HINTS "${HYPERXTB_ROOT}/build" "${HYPERXTB_ROOT}/build_linux"
NO_DEFAULT_PATH)
if(NOT HYPERXTB_LIB)
message(FATAL_ERROR
"libhyperxtb_core not found under ${HYPERXTB_ROOT}/build or build_linux")
endif()
find_library(HYPER_MACE_LIB mace
HINTS "${HYPER_MACE_ROOT}/build" "${HYPER_MACE_ROOT}/build_linux"
NO_DEFAULT_PATH)
if(NOT HYPER_MACE_LIB)
message(FATAL_ERROR
"libmace not found under ${HYPER_MACE_ROOT}/build or build_linux")
endif()
# libmace_cuda is optional — only present when hyper-mace was built with
# MACE_USE_CUDA=ON. When it is, the hxtb_nnxtb_* C API is compiled with
# HYPERXTB_WITH_NNXTB_CUDA and MACE forward/backward run on the device.
# libhyperxtb_core.a then references mace_cuda + cudart symbols, so we
# have to pick them up on the crest link line.
find_library(HYPER_MACE_CUDA_LIB mace_cuda
HINTS "${HYPER_MACE_ROOT}/build" "${HYPER_MACE_ROOT}/build_linux"
NO_DEFAULT_PATH)

add_library(hyperxtb::hyperxtb INTERFACE IMPORTED)
target_include_directories(hyperxtb::hyperxtb INTERFACE
"${HYPERXTB_ROOT}/include"
"${HYPER_MACE_ROOT}/include")
# hyperxtb_core references MACE symbols from its nnxtb_capi.o, so libmace
# must come after it on the link line. Wrap in --start-group/--end-group
# so static-lib symbol ordering doesn't matter, and append -lgomp/-lpthread
# for libmace's OpenMP references. C++ runtime needed for the hyper-xtb
# TU that's linked in.
if(HYPER_MACE_CUDA_LIB)
find_package(CUDAToolkit REQUIRED)
target_link_libraries(hyperxtb::hyperxtb INTERFACE
-Wl,--start-group
"${HYPERXTB_LIB}" "${HYPER_MACE_LIB}" "${HYPER_MACE_CUDA_LIB}"
gomp pthread
-Wl,--end-group
CUDA::cudart stdc++)
message(STATUS "hyper-xtb NN-xTB backend: ON + CUDA (hyper-xtb=${HYPERXTB_ROOT}, hyper-mace=${HYPER_MACE_ROOT})")
else()
target_link_libraries(hyperxtb::hyperxtb INTERFACE
-Wl,--start-group
"${HYPERXTB_LIB}" "${HYPER_MACE_LIB}"
gomp pthread
-Wl,--end-group
stdc++)
message(STATUS "hyper-xtb NN-xTB backend: ON (hyper-xtb=${HYPERXTB_ROOT}, hyper-mace=${HYPER_MACE_ROOT}, CPU-only)")
endif()
add_compile_definitions(WITH_HYPERXTB)
endif()

# GFN-FF
if(NOT TARGET "gfnff::gfnff" AND WITH_GFNFF)
find_package("gfnff" REQUIRED)
Expand Down Expand Up @@ -153,6 +223,7 @@ if(WITH_OBJECT AND NOT STATICBUILD)
$<$<BOOL:${WITH_LIBPVOL}>:pvol::pvol>
$<$<BOOL:${WITH_TOMLF}>:toml-f::toml-f>
$<$<BOOL:${WITH_LWONIOM}>:lwoniom::lwoniom>
$<$<BOOL:${WITH_HYPERXTB}>:hyperxtb::hyperxtb>
$<$<BOOL:${WITH_OpenMP}>:OpenMP::OpenMP_Fortran>
)

Expand Down Expand Up @@ -204,6 +275,7 @@ target_link_libraries(
$<$<BOOL:${WITH_LIBPVOL}>:pvol::pvol>
$<$<BOOL:${WITH_TOMLF}>:toml-f::toml-f>
$<$<BOOL:${WITH_LWONIOM}>:lwoniom::lwoniom>
$<$<BOOL:${WITH_HYPERXTB}>:hyperxtb::hyperxtb>
$<$<BOOL:${STATICBUILD}>:-static>
)

Expand Down Expand Up @@ -249,6 +321,7 @@ if (WITH_OBJECT AND NOT STATICBUILD)
$<$<BOOL:${WITH_LIBPVOL}>:pvol::pvol>
$<$<BOOL:${WITH_TOMLF}>:toml-f::toml-f>
$<$<BOOL:${WITH_LWONIOM}>:lwoniom::lwoniom>
$<$<BOOL:${WITH_HYPERXTB}>:hyperxtb::hyperxtb>
)

set_target_properties(
Expand Down
17 changes: 9 additions & 8 deletions config/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ install(
)

# Options for enabling or disabling features
option(WITH_OpenMP "Enable OpenMP support" TRUE)
option(WITH_TBLITE "Enable support for tblite" TRUE)
option(WITH_TOMLF "Enable support for toml-f" TRUE)
option(WITH_GFN0 "Enable support for GFN0-xTB" TRUE)
option(WITH_GFNFF "Enable support for GFN-FF" TRUE)
option(WITH_LIBPVOL "Enable support for LIBPVOL" TRUE)
option(WITH_LWONIOM "Enable support for lwONIOM" TRUE)
option(WITH_TESTS "Enable unit tests" TRUE)
option(WITH_OpenMP "Enable OpenMP support" TRUE)
option(WITH_TBLITE "Enable support for tblite" TRUE)
option(WITH_TOMLF "Enable support for toml-f" TRUE)
option(WITH_GFN0 "Enable support for GFN0-xTB" TRUE)
option(WITH_GFNFF "Enable support for GFN-FF" TRUE)
option(WITH_LIBPVOL "Enable support for LIBPVOL" TRUE)
option(WITH_LWONIOM "Enable support for lwONIOM" TRUE)
option(WITH_HYPERXTB "Enable hyper-xtb NN-xTB back-end (requires HYPERXTB_ROOT and HYPER_MACE_ROOT)" FALSE)
option(WITH_TESTS "Enable unit tests" TRUE)

option(STATICBUILD "Attempt to link everything statically" FALSE)
set(PYTHON_BINDINGS OFF CACHE BOOL "Disable python bindings for submodules that have them" FORCE)
Expand Down
76 changes: 76 additions & 0 deletions docs/hyper_crest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# hyper-crest: CREST with hyper-xtb NN-xTB

This fork adds `nnxtb` as a first-class CREST back-end, powered by
[hyper-xtb](https://github.com/talo/hyper-xtb) (NN-xTB branch) and
[hyper-mace](https://github.com/talo/hyper-mace) (ScaleShiftMACExTB).
The goal is CREST-speed conformer/rotamer sampling at DFT-level
accuracy by replacing Grimme xTB with a MACE-corrected GFN2-xTB.

## Build

You need built trees of hyper-xtb (`feat/nnxtb-capi` or later — the C API
is `include/hyperxtb_capi.h`'s `hxtb_nnxtb_*` entry points) and hyper-mace
(`xtb-implementation` branch). Both produce static libraries picked up
via `HYPERXTB_ROOT` / `HYPER_MACE_ROOT`.

```sh
# 1. hyper-mace — builds libmace.a
cd $HYPER_MACE_ROOT && cmake -B build . && cmake --build build -j

# 2. hyper-xtb with NN-xTB capi — builds libhyperxtb_core.a
cd $HYPERXTB_ROOT && cmake -B build . \
-DHYPERXTB_WITH_NNXTB=ON \
-DHYPER_MACE_ROOT=$HYPER_MACE_ROOT
cmake --build build -j

# 3. hyper-crest (this tree) with WITH_HYPERXTB
cd $HYPER_CREST_ROOT && cmake -B build . \
-DWITH_HYPERXTB=ON \
-DHYPERXTB_ROOT=$HYPERXTB_ROOT \
-DHYPER_MACE_ROOT=$HYPER_MACE_ROOT
cmake --build build -j
```

## Use

Select the new back-end via the TOML `method = "nnxtb"` and point at the
MACE model binary:

```toml
# input.toml
input = "mol.xyz"
runtype = "imtd-gc"

[calculation.level]
method = "nnxtb"
mace_model = "/path/to/ScaleShiftMACExTB.mxtb"
chrg = 0
```

```sh
crest input.toml
```

Also accepted via `create_calclevel_shortcut`: `crest mol.xyz --nnxtb`
(with the model path provided via TOML or future CLI flag).

## What changes under the hood

`src/calculator/hyperxtb_api.F90` binds to the hyper-xtb `hxtb_nnxtb_*`
C API via `iso_c_binding`. A single `HxtbNNxtbEngine*` is created during
`hyperxtb_setup` on the first energy/gradient request and reused for
every subsequent CREST evaluation in that run — MACE weights are loaded
exactly once per `crest` invocation, as required by the deployment
constraint for NN-xTB.

Per-call flow inside `hxtb_nnxtb_compute`:
1. Build all-pairs neighbor list at MACE cutoff (Å)
2. `mace_xtb_compute` → element/global parameter deltas
3. Clamp deltas to `[-0.5, 0.5]` and run `nn_xtb_forward`
(GFN2-xTB SCF with deltas applied)
4. `mace_xtb_backward` on `(d_elem, d_glob)` → Cartesian chain-rule
5. Combine xTB and MACE gradient contributions (Hartree/Bohr) in place

CREST sees a single dE/dR vector with the same sign convention as the
existing `tblite` and `gfn0` back-ends, so no downstream changes are
needed (optimizer, metadynamics, CREGEN, ensembles all just work).
1 change: 1 addition & 0 deletions src/calculator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ list(APPEND srcs
"${dir}/oniom_hessian.F90"
"${dir}/nonadiabatic.f90"
"${dir}/tblite_api.F90"
"${dir}/hyperxtb_api.F90"
"${dir}/api_helpers.F90"
"${dir}/api_engrad.f90"
"${dir}/gradreader.f90"
Expand Down
44 changes: 44 additions & 0 deletions src/calculator/api_engrad.f90
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ module api_engrad
use gfn0_api
use gfnff_api
use libpvol_api
use hyperxtb_api
use lj
implicit none
!>--- private module variables and parameters
Expand All @@ -43,6 +44,7 @@ module api_engrad
public :: gfn0_engrad,gfn0occ_engrad
public :: gfnff_engrad
public :: libpvol_engrad
public :: hyperxtb_engrad
public :: lj_engrad !> RE-EXPORT

!=========================================================================================!
Expand Down Expand Up @@ -116,6 +118,48 @@ subroutine tblite_engrad(mol,calc,energy,grad,iostatus)
return
end subroutine tblite_engrad

!========================================================================================!

subroutine hyperxtb_engrad(mol,calc,energy,grad,iostatus)
!**********************************************************
!* Interface singlepoint call between CREST and hyper-xtb's
!* NN-xTB engine. MACE weights are loaded on the first call
!* and reused for every subsequent evaluation — the whole
!* reason hyper-crest exists.
!**********************************************************
implicit none
type(coord) :: mol
type(calculation_settings) :: calc

real(wp),intent(inout) :: energy
real(wp),intent(inout) :: grad(3,mol%nat)
integer,intent(out) :: iostatus

iostatus = 0

!>--- one-time engine load. Mirrors the tblite_init / loadnew pattern so
!> the same bundle can be reused across conformers in a CREST run.
!$omp critical
if (.not.allocated(calc%hyperxtb)) then
allocate (calc%hyperxtb)
end if
if (.not.calc%hyperxtb%loaded) then
if (.not.allocated(calc%hyperxtb%mace_model_path) &
& .and. allocated(calc%hyperxtb_model)) then
calc%hyperxtb%mace_model_path = calc%hyperxtb_model
end if
call hyperxtb_setup(mol,calc%chrg,calc%hyperxtb)
end if
!$omp end critical

!>--- engrad call
call initsignal()
call hyperxtb_singlepoint(mol,calc%chrg,calc%hyperxtb,energy,grad,iostatus)
if (iostatus /= 0) return

return
end subroutine hyperxtb_engrad

!========================================================================================!

subroutine gfn0_engrad(mol,calc,g0calc,energy,grad,iostatus)
Expand Down
24 changes: 22 additions & 2 deletions src/calculator/calc_type.f90
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module calc_type
use gfn0_api
use gfnff_api,only:gfnff_data
use libpvol_api,only:libpvol_calculator
use hyperxtb_api,only:hyperxtb_data,hyperxtb_cleanup
!>--- other types
use orca_type
use lwoniom_module
Expand All @@ -49,10 +50,11 @@ module calc_type
integer :: gfnff = 9
integer :: libpvol = 10
integer :: lj = 11
integer :: hyperxtb = 12
end type enum_jobtype
type(enum_jobtype), parameter,public :: jobtype = enum_jobtype()

character(len=45),parameter,private :: jobdescription(12) = [ &
character(len=45),parameter,private :: jobdescription(13) = [ &
& 'Unknown calculation type ', &
& 'xTB calculation via external binary ', &
& 'Generic script execution ', &
Expand All @@ -64,7 +66,8 @@ module calc_type
& 'GFN0*-xTB calculation via GFN0 lib ', &
& 'GFN-FF calculation via GFNFF lib ', &
& 'external pressure calculation via libpvol ', &
& 'Lennard-Jones potential calculation ' ]
& 'Lennard-Jones potential calculation ', &
& 'NN-xTB via hyper-xtb + hyper-mace ' ]
!&>

!=========================================================================================!
Expand Down Expand Up @@ -160,6 +163,11 @@ module calc_type
!>--- GFN-FF data
type(gfnff_data),allocatable :: ff_dat

!>--- hyper-xtb (NN-xTB) data
type(hyperxtb_data),allocatable :: hyperxtb
!> Path to the ScaleShiftMACExTB `.mxtb` model file. User-supplied.
character(len=:),allocatable :: hyperxtb_model

!>--- libpvol data
integer :: pvmodel = 1 !> libpvol model type (0=XHCFF, 1=PV)
integer :: ngrid = 1202 !> lebedev grid points per atom
Expand Down Expand Up @@ -357,6 +365,10 @@ subroutine calculation_deallocate_params(self)
if(allocated(self%calcs(i)%g0calc)) deallocate(self%calcs(i)%g0calc)
if(allocated(self%calcs(i)%ff_dat)) deallocate(self%calcs(i)%ff_dat)
if(allocated(self%calcs(i)%libpvol)) deallocate(self%calcs(i)%libpvol)
if(allocated(self%calcs(i)%hyperxtb)) then
call hyperxtb_cleanup(self%calcs(i)%hyperxtb)
deallocate(self%calcs(i)%hyperxtb)
end if
end do
end if
end subroutine calculation_deallocate_params
Expand Down Expand Up @@ -918,6 +930,11 @@ subroutine calculation_settings_deallocate(self)
if (allocated(self%g0calc)) deallocate (self%g0calc)
if (allocated(self%ff_dat)) deallocate (self%ff_dat)
if (allocated(self%libpvol)) deallocate (self%libpvol)
if (allocated(self%hyperxtb)) then
call hyperxtb_cleanup(self%hyperxtb)
deallocate (self%hyperxtb)
end if
if (allocated(self%hyperxtb_model)) deallocate (self%hyperxtb_model)

self%id = 0
self%prch = stdout
Expand Down Expand Up @@ -1229,6 +1246,9 @@ subroutine create_calclevel_shortcut(self,levelstring)
case ('generic')
self%id = jobtype%generic

case ('nnxtb','--nnxtb','hyperxtb','--hyperxtb')
self%id = jobtype%hyperxtb

end select
call self%autocomplete(self%id)
end subroutine create_calclevel_shortcut
Expand Down
4 changes: 4 additions & 0 deletions src/calculator/calculator.F90
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,10 @@ subroutine potential_core(molptr,calc,id,iostatus)
!> GFN-FF api
call gfnff_engrad(molptr,calc%calcs(id),calc%etmp(id),calc%grdtmp(:,1:pnat,id),iostatus)

case (jobtype%hyperxtb)
!> hyper-xtb NN-xTB api (MACE weights loaded once, reused here)
call hyperxtb_engrad(molptr,calc%calcs(id),calc%etmp(id),calc%grdtmp(:,1:pnat,id),iostatus)

case (jobtype%libpvol)
!> libpvol
call libpvol_engrad(molptr,calc%calcs(id),calc%etmp(id),calc%grdtmp(:,1:pnat,id),iostatus)
Expand Down
Loading