Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
baa4636
fix(meson): banner reports LAMMPS as enabled when always compiled
HaoZeke May 9, 2026
5c8ce60
docs(lammps): clarify in.lammps file placement (potfiles/ vs CWD) + f…
HaoZeke May 10, 2026
ea49279
fix(subprojects): pin rgpot.wrap to v1.1.0
HaoZeke May 10, 2026
ffccbcc
feat(vasp): always compile on POSIX, drop with_vasp option
HaoZeke May 10, 2026
df7ee8d
feat(artn,ira): drop with_artn / with_ira, dlopen libartn / libira
HaoZeke May 10, 2026
180e531
feat(lammps): portable run-input bundle + fail-loud on missing in.lammps
HaoZeke May 10, 2026
d1539fb
docs(user_guide): bundle workflow for LAMMPS, runtime-loaded ARTn/IRA…
HaoZeke May 10, 2026
9773f6b
docs(news): towncrier fragments for the runtime-load + bundle work
HaoZeke May 10, 2026
9f2c41c
feat(xtb): drop with_xtb, dlopen libxtb at runtime via XtbLoader
HaoZeke May 10, 2026
35d4a62
fix(mpi): port MPIPot + ClientEON off the removed C++ bindings
HaoZeke May 10, 2026
9ac89dd
feat(mpi): prefer MPItrampoline for runtime-portable MPI
HaoZeke May 10, 2026
5270adf
fix(min): bail out of LBFGS / CG on non-finite forces
HaoZeke May 10, 2026
1dbc9d3
feat(blas): integrate FlexiBLAS for runtime-pluggable BLAS / LAPACK
HaoZeke May 10, 2026
56a5ddb
build(meson): use sourceset for eonclib (closes #177)
HaoZeke May 10, 2026
f3758d0
style: apply clang-format to runtime-load + bundle work
HaoZeke May 10, 2026
90653ba
fix(build): link -ldl globally, allow no-tests Catch2 exit, optional …
HaoZeke May 10, 2026
b908afc
fix(metatomic): pre-check energy_uncertainty key, no c10::Error spam …
HaoZeke May 10, 2026
c3656d6
docs(install): document eonclient --features for runtime introspectio…
HaoZeke May 10, 2026
e015014
chore(build): drop with_xtb=False/True from pixi + ci_xtb.yml
HaoZeke May 10, 2026
04b9059
build(meson): warning_level=1, hidden visibility, pybind11 lookup onc…
HaoZeke May 10, 2026
0e0b648
perf(metatomic): precompile the torch + metatensor + metatomic header…
HaoZeke May 10, 2026
ee0df45
build(tidy): baseline .clang-tidy ruleset
HaoZeke May 10, 2026
8d4ff80
ci: ASan + UBSan sanitizer workflow
HaoZeke May 10, 2026
9806a95
chore(api): nodiscard on DynLib helpers, document LAMMPSPot threading
HaoZeke May 10, 2026
8b85725
chore(min): LBFGS_EPS becomes inline constexpr with rationale
HaoZeke May 10, 2026
b575087
fix(build): revert -fvisibility=hidden, migrate 10 deprecated ctor calls
HaoZeke May 10, 2026
239dc60
fix(build): zero -Wall warnings (-Wstringop-overread + shared_ptr move)
HaoZeke May 10, 2026
57864ec
build(meson): bump warning_level to 2 (-Wall -Wextra), zero warnings
HaoZeke May 10, 2026
b6fdb38
chore(precommit): trim mastereqn from exclude regex
HaoZeke May 10, 2026
d22d4fe
fix(tidy): zero-init sigaction + std::stoi for filename parsing
HaoZeke May 10, 2026
cc0c05c
chore(tidy): clang-tidy auto-fixes for safe modernize/readability checks
HaoZeke May 10, 2026
137fadf
chore(deps): bump readcon to v0.9.0; drop ensure_cbindgen task
HaoZeke May 10, 2026
39e91e6
chore(deps): refresh pixi.lock for readcon v0.9.0
HaoZeke May 10, 2026
f5a2cdc
fix(tidy): check return values on stdio I/O paths (cert-err33-c)
HaoZeke May 10, 2026
ec197a4
chore(tidy): batch fixes for performance + bugprone + manual-lint hits
HaoZeke May 10, 2026
915dd04
refactor(types): introduce StepResult enum class for Optimizer returns
HaoZeke May 10, 2026
2b97ed5
fix(neb): drop unused `int status` from idpp_optim->run()
HaoZeke May 10, 2026
1e1e70f
refactor(types): SaddleStatus enum class for SaddleSearchMethod hiera…
HaoZeke May 10, 2026
11a7e8c
fix(types): using eonc::SaddleStatus in saddle-search .cpp TUs
HaoZeke May 10, 2026
dccf2f2
fix(types): typed status in DynamicsSaddleSearch::refineTransition
HaoZeke May 10, 2026
1843ee4
fix(types): BasinHoppingSaddleSearch::run returns SaddleStatus::Good
HaoZeke May 10, 2026
16c837b
fix(types): BasinHoppingSaddleSearch reject path returns SaddleStatus…
HaoZeke May 10, 2026
aaae987
chore(tidy): residual implicit-widening + value-param + roundings + M…
HaoZeke May 10, 2026
ce4f17d
refactor(py): SaddleStatus IntEnum mirror in eon.status_codes
HaoZeke May 10, 2026
41ed8ad
fix(test): migrate SaddleSearchTest + ci_docs to typed status
HaoZeke May 10, 2026
8f226ab
fix(test): operator<< for status enums; finish JobIntegrationTest + O…
HaoZeke May 10, 2026
76a3980
fix(ci): restore cbindgen install step for docs build
HaoZeke May 10, 2026
19bea78
fix(build): install eon/status_codes.py in the Python package
HaoZeke May 10, 2026
2da0fb5
fix(test): add libira / libartn runtime SKIP guards
HaoZeke May 10, 2026
5ca562f
chore(deps): bump readcon to v0.10.0 (energies section, atom_id index…
HaoZeke May 10, 2026
fc1005d
fix(loader): leak dlopen'd handles at process exit; do not dlclose in…
HaoZeke May 10, 2026
c0ca6de
fix(loader): apply same dlclose-skip policy to LAMMPS / ARTn / IRA lo…
HaoZeke May 10, 2026
909e44e
fix(min): non-finite force bail without charging extra force calls
HaoZeke May 10, 2026
d60a9b8
build(test): catch2 as static lib + bench_pot off by default
HaoZeke May 10, 2026
231451a
docs(sphinx): fix four sphinx-build warnings
HaoZeke May 10, 2026
1762b72
fix(test): drop unused testMain library leftover from catch2_lib refa…
HaoZeke May 10, 2026
64975fa
fix(test): link_whole the catch2 static lib so LTO keeps main()
HaoZeke May 10, 2026
c66b290
fix(test): MSVC rejects -Wno-maybe-uninitialized; gate on supported f…
HaoZeke May 10, 2026
cbc0991
fix(test): fall back to per-target catch2 build on windows
HaoZeke May 10, 2026
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
51 changes: 51 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
# eOn baseline clang-tidy ruleset.
#
# Scope: high-signal categories only -- `bugprone-*` (real bugs),
# `cert-*` (security / undefined-behaviour), `modernize-use-nullptr`
# / `modernize-use-override` / `modernize-use-equals-default`
# (cheap C++20 hygiene), `readability-redundant-*` and
# `readability-qualified-auto` (low-risk cleanup),
# `cppcoreguidelines-pro-type-cstyle-cast` and -member-init.
#
# Deliberately excluded: `cppcoreguidelines-avoid-magic-numbers`
# (atomistic constants are intrinsically magic), -pro-bounds-* (too
# noisy for Eigen-heavy code), `modernize-use-trailing-return-type`
# (style preference, not a bug class), `fuchsia-*`, `llvm-*`,
# `google-readability-todo` (we use TODO(rg)).
#
# The vendored thirdparty/ tree is excluded via .clang-tidy in that
# directory inheriting `Checks: '-*'`.
Checks: >
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions,
cert-*,
-cert-err58-cpp,
cppcoreguidelines-pro-type-cstyle-cast,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-slicing,
cppcoreguidelines-virtual-class-destructor,
modernize-use-nullptr,
modernize-use-override,
modernize-use-equals-default,
modernize-use-equals-delete,
modernize-redundant-void-arg,
modernize-deprecated-headers,
performance-for-range-copy,
performance-implicit-conversion-in-loop,
performance-inefficient-vector-operation,
performance-unnecessary-value-param,
readability-qualified-auto,
readability-redundant-control-flow,
readability-redundant-string-cstr,
readability-redundant-string-init,
readability-redundant-smartptr-get
WarningsAsErrors: ""
HeaderFilterRegex: "client/(?!thirdparty/).*"
FormatStyle: file
CheckOptions:
- key: readability-qualified-auto.AddConstToQualified
value: "true"
- key: modernize-use-override.IgnoreDestructors
value: "true"
5 changes: 4 additions & 1 deletion .github/workflows/ci_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ jobs:
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');

- name: Install cbindgen
shell: bash
run: command -v cbindgen || cargo install cbindgen

- name: Build and install eonclient
shell: pixi run -e docs-mta bash -e {0}
run: |
pixi run ensure_cbindgen
meson setup bbdir --prefix=$CONDA_PREFIX --libdir=lib \
-Dwith_metatomic=True -Dtorch_version=2.10 -Dpip_metatomic=True
meson install -C bbdir
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/ci_sanitizers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Sanitizers (ASan + UBSan)
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
on: [push, pull_request]
jobs:
sanitize:
runs-on: ubuntu-22.04
name: sanitize (asan+ubsan)
steps:
- uses: actions/checkout@v4
- uses: prefix-dev/setup-pixi@v0.9.4
with:
cache: true
cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }}
environments: dev-lite
- name: Install cbindgen
shell: bash
run: command -v cbindgen || cargo install cbindgen
- name: Build with ASan + UBSan
shell: pixi run bash -e {0}
run: |
# b_sanitize=address,undefined wires both runtimes through
# meson; b_lundef=false relaxes -Wl,--no-undefined which
# ASan's wrapper symbols don't resolve at link time.
meson setup bbdir-san \
--buildtype=debug \
-Db_sanitize=address,undefined \
-Db_lundef=false \
-Dwith_tests=true
meson compile -C bbdir-san
- name: Run tests under sanitizers
shell: pixi run bash -e {0}
env:
ASAN_OPTIONS: halt_on_error=1:abort_on_error=1:print_summary=1:detect_leaks=0
UBSAN_OPTIONS: halt_on_error=1:abort_on_error=1:print_stacktrace=1
run: |
# detect_leaks=0 because Eigen's static-init pool legitimately
# holds memory until process exit; we care about UAF / OOB /
# signed-overflow / null-deref, not leaks.
meson test -C bbdir-san --suite eon --print-errorlogs
5 changes: 4 additions & 1 deletion .github/workflows/ci_xtb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ jobs:
- name: Install eon
shell: pixi run bash -e {0}
run: |
# XTB is unconditionally compiled and dlopens libxtb at run
# time via XtbLoader -- no -Dwith_xtb needed. The dev-xtb
# pixi env supplies libxtb on LD_LIBRARY_PATH so test_xtb /
# test_cineb_xtb run; on envs without it the tests SKIP.
meson setup --reconfigure bbdir \
--prefix=$CONDA_PREFIX \
--buildtype release \
--libdir=lib \
-Dwith_xtb=True \
-Dwith_tests=True
meson install -C bbdir
- name: Run tests
Expand Down
9 changes: 4 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
fail_fast: false
exclude: |
(?x)(
thirdparty| # vendored
xtb.h| # vendored
mastereqn| # legacy (?)
libqd| # vendored / legacy (?)
approval_tests| # autogenerated
thirdparty| # vendored (catch2, vesin, argum, ...)
xtb.h| # vendored xtb C API header
libqd| # vendored quad-double in eon/mcamc/
approval_tests| # autogenerated reference data
newsfragments| # from towncrier
_static # no need to touch
)
Expand Down
64 changes: 59 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,22 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# ---------------------- Options (matching meson_options.txt)
option(WITH_WATER "Build Water potentials" OFF)
option(WITH_AMS "Build AMS potentials" OFF)
option(WITH_LAMMPS "Build LAMMPS potential" OFF)
option(WITH_MPI "Build with MPI support" OFF)
# LAMMPS, ARTn, IRA, XTB, and VASP have no build-time toggle: they
# are unconditionally compiled and dlopen / fork their runtime
# library / binary on first use. Mirrors meson_options.txt.
option(WITH_MPI "Build with MPI support (via MPItrampoline)" OFF)
option(WITH_GPRD "Build with GPR dimer" OFF)
option(WITH_VASP "Build VASP potential" OFF)
option(WITH_FORTRAN "Build Fortran potentials" ON)
option(WITH_CUH2 "Build CuH2 potential" ON)
option(WITH_TESTS "Build tests" ON)
option(WITH_GP_SURROGATE "Build GP surrogate" OFF)
option(WITH_CATLEARN "Build CatLearn potential" OFF)
option(WITH_XTB "Build XTB potential" OFF)
option(WITH_ASE_ORCA "Build ASE ORCA potential" OFF)
option(WITH_ASE_NWCHEM "Build ASE NWChem potential" OFF)
option(WITH_ASE "Build ASE potential" OFF)
option(WITH_QSC "Build QSC potential" OFF)
option(USE_MKL "Enable Intel MKL support" OFF)
option(USE_MKL "Hard-link Eigen against Intel MKL (legacy; mutually exclusive with WITH_FLEXIBLAS)" OFF)
option(WITH_FLEXIBLAS "Link Eigen via FlexiBLAS for runtime-pluggable BLAS / LAPACK" OFF)
option(WITH_METATOMIC "Build Metatomic potential" OFF)
option(WITH_PYTHON "Build Python-embedding potentials" OFF)
set(TORCH_PATH "" CACHE STRING "Path to Torch installation")
Expand Down Expand Up @@ -82,5 +83,58 @@ else()
find_package(Python3 COMPONENTS Interpreter)
endif()

# ---------------------- FlexiBLAS (BLAS / LAPACK)
#
# FlexiBLAS is the FlexiBLAS-of-MPI for BLAS: link once, swap the
# backend (OpenBLAS, MKL, BLIS, ATLAS, ARMPL, ...) at run time via
# the FLEXIBLAS env var. Mirrors the meson with_flexiblas block in
# client/meson.build and the MPItrampoline pattern below.
if(WITH_FLEXIBLAS AND USE_MKL)
message(FATAL_ERROR
"WITH_FLEXIBLAS and USE_MKL are mutually exclusive; "
"FlexiBLAS can route to MKL at run time via FLEXIBLAS=INTELMKL.")
endif()
if(WITH_FLEXIBLAS)
find_package(flexiblas QUIET)
if(NOT flexiblas_FOUND)
include(FetchContent)
FetchContent_Declare(
flexiblas
GIT_REPOSITORY https://github.com/mpimd-csc/flexiblas.git
GIT_TAG v3.5.0
GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(flexiblas)
endif()
endif()

# ---------------------- MPI (via MPItrampoline)
#
# MPI integration always goes through MPItrampoline -- the
# FlexiBLAS-of-MPI. We link against its stable wrapper ABI and one
# eonclient binary forwards every MPI_* call to any spec-compliant
# libmpi at run time, picked via MPITRAMPOLINE_LIB. Mirrors the
# meson MPI block in client/meson.build.
#
# The fall-through here matches meson's wrap-driven behaviour:
# 1. find_package(MPItrampoline) -- system or
# $CMAKE_PREFIX_PATH-resolvable install.
# 2. FetchContent from upstream v5.5.1 -- analogue of the
# subprojects/mpitrampoline.wrap. There is never a
# "no MPI library" failure mode; we deliberately do NOT fall
# back to FindMPI / a direct system MPI link, since that
# ABI-locks the binary to one flavour and defeats the point.
if(WITH_MPI)
find_package(MPItrampoline QUIET)
if(NOT MPItrampoline_FOUND)
include(FetchContent)
FetchContent_Declare(
MPItrampoline
GIT_REPOSITORY https://github.com/eschnett/MPItrampoline.git
GIT_TAG v5.5.1
GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(MPItrampoline)
endif()
endif()

# ---------------------- Subdirectories
add_subdirectory(client)
68 changes: 27 additions & 41 deletions client/ARTnSaddleSearch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,19 @@ ARTnSaddleSearch::ARTnSaddleSearch(std::shared_ptr<Matter> matterPassed,
std::shared_ptr<Potential> potPassed,
AtomMatrix modeInitial,
const Parameters &paramsPassed)
: SaddleSearchMethod(potPassed, paramsPassed),
matter{matterPassed},
mode{modeInitial},
eigenvector{AtomMatrix::Zero(matterPassed->numberOfAtoms(), 3)} {
: SaddleSearchMethod(std::move(potPassed), paramsPassed),
matter{std::move(matterPassed)},
eigenvector{AtomMatrix::Zero(matter->numberOfAtoms(), 3)},
mode{std::move(modeInitial)} {
log = eonc::log::get();
if (!log) {
throw std::runtime_error("ARTnSaddleSearch: Logger not initialized");
}
}

ARTnSaddleSearch::~ARTnSaddleSearch() {
#ifdef WITH_ARTN
// Clean up is done within the search loop, not in destructor
#endif
}
ARTnSaddleSearch::~ARTnSaddleSearch() = default;

int ARTnSaddleSearch::run() {
#ifdef WITH_ARTN
SaddleStatus ARTnSaddleSearch::run() {
auto &res = get_artn_resource();
const int nat = matter->numberOfAtoms();

Expand Down Expand Up @@ -81,7 +76,7 @@ int ARTnSaddleSearch::run() {
res.require_loaded();
} catch (const std::exception &e) {
QUILL_LOG_ERROR(log, "ARTn library not available: {}", e.what());
status = STATUS_BAD_ARTN_ERROR;
status = SaddleStatus::BadArtnError;
return status;
}

Expand Down Expand Up @@ -123,7 +118,7 @@ int ARTnSaddleSearch::run() {
if (!std::filesystem::exists(filin)) {
QUILL_LOG_ERROR(log, "artn_options.filin '{}' does not exist", filin);
res.get_destroy_fn()();
status = STATUS_BAD_ARTN_ERROR;
status = SaddleStatus::BadArtnError;
return status;
}
int result_filin =
Expand Down Expand Up @@ -208,7 +203,7 @@ int ARTnSaddleSearch::run() {
if (cerr) {
QUILL_LOG_ERROR(log, "ARTn setup failed (nat={})", nat);
res.get_destroy_fn()();
status = STATUS_BAD_ARTN_ERROR;
status = SaddleStatus::BadArtnError;
return status;
}

Expand All @@ -227,14 +222,19 @@ int ARTnSaddleSearch::run() {
}

// Per-atom metadata for the Fortran step (no pARTn state, unlocked).
std::vector<int> ityp(nat);
std::vector<int> if_pos(3 * nat, 1); // all atoms free by default
// size_t casts on nat * <small int> to avoid the implicit widening
// bugprone-implicit-widening-of-multiplication-result lint --
// every consumer wants size_t / Index / ptrdiff_t.
const std::size_t natz = static_cast<std::size_t>(nat);
std::vector<int> ityp(natz);
std::vector<int> if_pos(natz * 3, 1); // all atoms free by default
double box_f[9];
bool lconv = false;

for (int i = 0; i < nat; i++) {
if (matter->getFixed(i)) {
Eigen::Map<Eigen::Vector3i>(&if_pos[i * 3]).setZero();
Eigen::Map<Eigen::Vector3i>(&if_pos[static_cast<std::size_t>(i) * 3])
.setZero();
}
ityp[i] = matter->getAtomicNr(i);
}
Expand Down Expand Up @@ -346,7 +346,10 @@ int ARTnSaddleSearch::run() {
"tau_sad", reinterpret_cast<void **>(&tau_sad_ptr));
if (result_tau_sad == 0 && tau_sad_ptr) {
matter->setPositions(eonc::from_fortran_layout_vector(
std::vector<double>(tau_sad_ptr, tau_sad_ptr + 3 * nat), nat));
std::vector<double>(tau_sad_ptr,
tau_sad_ptr +
static_cast<std::ptrdiff_t>(3) * nat),
nat));
std::free(tau_sad_ptr);
} else {
QUILL_LOG_WARNING(
Expand Down Expand Up @@ -380,7 +383,9 @@ int ARTnSaddleSearch::run() {
if (result_evec == 0 && evec_ptr) {
// Use direct Eigen::Map to convert from Fortran layout
eigenvector = eonc::from_fortran_layout_vector(
std::vector<double>(evec_ptr, evec_ptr + 3 * nat), nat);
std::vector<double>(
evec_ptr, evec_ptr + static_cast<std::ptrdiff_t>(3) * nat),
nat);

// get_data allocates via c_malloc (artn_c_wrappers.f90), safe to free
std::free(evec_ptr);
Expand All @@ -392,7 +397,7 @@ int ARTnSaddleSearch::run() {
eigenvector = AtomMatrix::Zero(nat, 3);
}

status = STATUS_GOOD;
status = SaddleStatus::Good;
res.get_destroy_fn()();
return status;
}
Expand All @@ -401,27 +406,21 @@ int ARTnSaddleSearch::run() {
QUILL_LOG_WARNING(
log, "ARTn stopped after {} iterations (has_error={}, has_sad={})",
iteration, has_error, has_sad);
status = STATUS_BAD_ARTN_ERROR;
status = SaddleStatus::BadArtnError;
res.get_destroy_fn()();
return status;
}

QUILL_LOG_WARNING(log, "ARTn did not converge after {} iterations",
iteration);
status = STATUS_BAD_MAX_ITERATIONS;
status = SaddleStatus::BadMaxIterations;

// Clean up in all cases
{
std::lock_guard<std::mutex> lock(res.library_mutex);
res.get_destroy_fn()();
}
return status;

#else
QUILL_LOG_ERROR(log, "ARTn support not compiled");
status = STATUS_BAD_ARTN_ERROR;
return status;
#endif
}

double ARTnSaddleSearch::getEigenvalue() {
Expand All @@ -438,17 +437,4 @@ AtomMatrix ARTnSaddleSearch::getEigenvector() {
return eigenvector;
}

std::string_view ARTnSaddleSearch::describeStatus(int status) const {
switch (status) {
case STATUS_GOOD:
return "Success";
case STATUS_BAD_MAX_ITERATIONS:
return "Too many iterations";
case STATUS_BAD_ARTN_ERROR:
return "ARTn backend error";
default:
return "Unknown status";
}
}

} // namespace eonc
Loading
Loading