28#if defined(__linux__) && defined(EON_RGPOT_MPI)
33#include <capnp/message.h>
34#include <capnp/serialize.h>
38#include "rgpot/CPMDPot/CPMDPot.hpp"
39#include "rgpot/CalculatorGroup.hpp"
40#include "rgpot/NWChemPot/NWChemPot.hpp"
41#include "rgpot/rpc/Potentials.capnp.h"
43using rgpot::types::AtomMatrix;
45#if defined(__linux__) && defined(EON_RGPOT_MPI)
50extern "C" void eon_rgpot_hard_exit(
int status,
void *) {
53 MPI_Initialized(&inited);
54 MPI_Finalized(&finalized);
55 if (inited && !finalized) {
56 if (::rgpot::mpiAbortRequested()) {
58 MPI_Abort(MPI_COMM_WORLD, status != 0 ? status : 1);
71std::vector<::capnp::word> read_params_file(
const std::string &path) {
72 std::ifstream in(path, std::ios::binary | std::ios::ate);
74 throw std::runtime_error(
"RGPOT: cannot open params_path: " + path);
75 const std::streamsize bytes = in.tellg();
76 if (bytes <= 0 || (
static_cast<size_t>(bytes) %
sizeof(::capnp::word)) != 0)
77 throw std::runtime_error(
78 "RGPOT: params_path is not a capnp flat message: " + path);
79 std::vector<::capnp::word> words(
static_cast<size_t>(bytes) /
80 sizeof(::capnp::word));
82 in.read(
reinterpret_cast<char *
>(words.data()), bytes);
84 throw std::runtime_error(
"RGPOT: short read on params_path: " + path);
88std::string to_lower(std::string s) {
90 c =
static_cast<char>(std::tolower(
static_cast<unsigned char>(c)));
94std::array<std::array<double, 3>, 3> box_from_row_major(
const double *box) {
95 std::array<std::array<double, 3>, 3> out{};
96 for (
int i = 0; i < 3; ++i)
97 for (
int j = 0; j < 3; ++j)
98 out[
static_cast<size_t>(i)][
static_cast<size_t>(j)] = box[i * 3 + j];
102bool looks_like_dft_xc(
const std::string &s) {
105 static const char *k[] = {
"b3lyp",
"blyp",
"pbe",
"pw91",
"bp86",
106 "hcth",
"ft97",
"hfexch",
"xperpbe",
nullptr};
107 for (
int i = 0; k[i]; ++i) {
108 if (s.size() >= std::char_traits<char>::length(k[i]) &&
109 s.compare(0, std::char_traits<char>::length(k[i]), k[i]) == 0)
116int xtb_method_from_paramset(
const std::string ¶mset) {
117 const std::string p = to_lower(paramset);
118 if (p ==
"gfnff" || p ==
"gfn-ff")
120 if (p ==
"gfn0xtb" || p ==
"gfn0" || p ==
"gfn0-xtb")
122 if (p ==
"gfn1xtb" || p ==
"gfn1" || p ==
"gfn1-xtb")
124 if (p ==
"gfn2xtb" || p ==
"gfn2" || p ==
"gfn2-xtb" || p.empty())
126 throw std::runtime_error(
127 "RGPOT(xtb): paramset must be GFNFF, GFN0xTB, GFN1xTB, or GFN2xTB "
134int mpi_world_hint() {
135 const char *s = std::getenv(
"OMPI_COMM_WORLD_SIZE");
136 if (s ==
nullptr || s[0] ==
'\0')
137 s = std::getenv(
"PMI_SIZE");
138 if (s ==
nullptr || s[0] ==
'\0')
143void pin_cpmd_library(
const std::string &path) {
147 const char *name = path.empty() ?
"libcpmdc.so" : path.c_str();
148 dlopen(name, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL | RTLD_NODELETE);
154#if defined(__linux__) && defined(EON_RGPOT_MPI)
158bool agree_construction(std::string &message) {
160 MPI_Initialized(&inited);
162 MPI_Init(
nullptr,
nullptr);
163 ::rgpot::finalizeMpiAtExit();
164 const int ok = message.empty() ? 1 : 0;
166 MPI_Allreduce(&ok, &all_ok, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
171 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
172 MPI_Comm_size(MPI_COMM_WORLD, &size);
173 const int mine = ok ? size : rank;
175 MPI_Allreduce(&mine, &owner, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
178 len =
static_cast<int>(
179 std::min(message.size(),
static_cast<std::size_t
>(4095)));
180 MPI_Bcast(&len, 1, MPI_INT, owner, MPI_COMM_WORLD);
181 std::vector<char> buf(
static_cast<std::size_t
>(len) + 1,
'\0');
182 if (rank == owner && len > 0)
183 std::memcpy(buf.data(), message.data(),
static_cast<std::size_t
>(len));
185 MPI_Bcast(buf.data(), len, MPI_CHAR, owner, MPI_COMM_WORLD);
186 message.assign(buf.data(),
static_cast<std::size_t
>(len));
196 ::CPMDParams::Builder params = msg.initRoot<::CPMDParams>();
198 const auto words = read_params_file(opt.
params_path);
199 ::capnp::FlatArrayMessageReader reader(
200 kj::arrayPtr(words.data(), words.size()));
201 msg.setRoot(reader.getRoot<::CPMDParams>());
202 params = msg.getRoot<::CPMDParams>();
206 params.setCharge(opt.
charge);
208 if (!opt.
title.empty())
209 params.setTitle(opt.
title);
211 params.setMemoryMb(
static_cast<uint32_t
>(opt.
memory_mb));
226 if (
const char *env = std::getenv(
"RGPOT_CPMD_INPUT_BLOCK"))
229 if (!block.empty()) {
230 std::vector<std::string> kept;
232 auto existing = params.getInputBlocks();
233 kept.reserve(existing.size());
234 for (
auto text : existing)
235 kept.emplace_back(text.cStr());
237 auto blocks = params.initInputBlocks(kept.size() + 1);
238 for (std::size_t i = 0; i < kept.size(); ++i)
239 blocks.set(i, kept[i]);
240 blocks.set(kept.size(), block);
249 std::unique_ptr<rgpot::NWChemPot>
nwchem;
250 std::unique_ptr<rgpot::CPMDPot>
cpmd;
252 std::unique_ptr<XTBEngineLoader>
xtb;
267 ::capnp::MallocMessageBuilder msg;
268 auto params = msg.initRoot<::NWChemParams>();
269 params.setBasis(opt.
basis);
270 params.setTheory(opt.
theory);
272 params.setCharge(opt.
charge);
280 if (!opt.
title.empty())
281 params.setTitle(opt.
title);
283 params.setMemoryMb(
static_cast<uint32_t
>(opt.
memory_mb));
289 if (
const char *env = std::getenv(
"RGPOT_NWCHEM_INPUT_BLOCK"))
292 if (block.empty() && (opt.
theory ==
"dft" || opt.
theory ==
"DFT") &&
294 block =
"dft\n xc " + opt.
scf_type +
"\n mult " +
296 }
else if (block.empty() && looks_like_dft_xc(opt.
theory)) {
297 block =
"dft\n xc " + opt.
theory +
"\n mult " +
300 if (!block.empty()) {
301 auto blocks = params.initInputBlocks(1);
302 blocks.set(0, block);
304 impl_->nwchem = std::make_unique<rgpot::NWChemPot>(params.asReader());
305 if (!
impl_->nwchem->available())
306 throw std::runtime_error(
307 "RGPOT(nwchemc): engine not available (set NWCHEMC_LIBRARY / "
308 "RGPOT_NWCHEMC_ENGINE or [RgpotPot] engine_path)");
313 std::string local_error;
315 ::capnp::MallocMessageBuilder msg;
317 impl_->cpmd = std::make_unique<rgpot::CPMDPot>(params.asReader());
320 if (!
impl_->cpmd->available())
321 throw std::runtime_error(
322 "RGPOT(cpmdc): engine not available (set CPMDC_LIBRARY / "
323 "RGPOT_CPMDC_ENGINE or [RgpotPot] engine_path)");
324 }
catch (
const std::exception &ex) {
325 local_error = ex.what();
329 if (mpi_world_hint() <= 1 && !local_error.empty())
330 throw std::runtime_error(local_error);
331#if defined(__linux__) && defined(EON_RGPOT_MPI)
334 if (mpi_world_hint() > 1 && !agree_construction(local_error)) {
336 throw std::runtime_error(
339 "RGPOT(cpmdc): a rank failed before the calculator split")
343 if (!local_error.empty())
344 throw std::runtime_error(local_error);
345 if (::rgpot::calculatorsUseMpi()) {
349 const rgpot::CalculatorGroup g =
351 ::rgpot::finalizeMpiAtExit();
352 if (mpi_world_hint() > 1)
355 throw std::runtime_error(
356 "RGPOT(cpmdc): ranks_per_image=" +
358 " does not divide the MPI world into calculator groups");
359 impl_->groups = ::rgpot::calculatorCount();
360 impl_->group = g.index;
361 impl_->world = ::rgpot::calculatorWorldSize();
363 throw std::runtime_error(
364 "RGPOT(cpmdc): ranks_per_image needs rgpot built with MPI "
365 "(-Drgpot:with_mpi=enabled)");
381 impl_->metatomic = std::make_unique<MetatomicEngineLoader>(mopt);
382 if (!
impl_->metatomic->available())
383 throw std::runtime_error(
384 "RGPOT(metatomic): engine not available (set RGPOT_METATOMIC_ENGINE "
385 "or [RgpotPot] engine_path to libmetatomic_engine.so)");
399 impl_->xtb = std::make_unique<XTBEngineLoader>(xopt);
400 if (!
impl_->xtb->available())
401 throw std::runtime_error(
402 "RGPOT(xtb): engine not available (set RGPOT_XTB_ENGINE or "
403 "[RgpotPot] engine_path to libxtb_engine.so)");
405 throw std::runtime_error(
"RGPOT: unknown backend '" + opt.
backend +
406 "' (expected nwchemc, cpmdc, metatomic, or xtb)");
427 const rgpot::CalculatorGroup &g = ::rgpot::thisCalculator();
428 return g.index * g.ranks + g.rank_in_group;
433 ::rgpot::finalizeMpiAtExit();
437 return ::rgpot::mpiAbortRequested();
441#if defined(__linux__) && defined(EON_RGPOT_MPI)
442 if (mpi_world_hint() <= 1)
444 static std::once_flag once;
445 std::call_once(once, [] { ::on_exit(eon_rgpot_hard_exit,
nullptr); });
454 impl_->module_down =
true;
457 reinterpret_cast<void (*)()
>(dlsym(RTLD_DEFAULT,
"cpmdc_finalize")))
459 pin_cpmd_library({});
464 if (!
impl_ ||
impl_->world <= 1 || bytes == 0)
467 if (::rgpot::shareFromCalculator(0, data, bytes) == 0)
468 throw std::runtime_error(
"RGPOT: could not broadcast from the driver rank");
472 std::string &error)
const {
479 std::vector<double> buf(
static_cast<size_t>(3 * N + 2));
480 if (owner ==
impl_->group) {
481 buf[0] = ok ? 1.0 : 0.0;
483 std::copy(F, F + 3 * N, buf.begin() + 2);
485 if (::rgpot::shareFromCalculator(owner, buf.data(),
486 buf.size() *
sizeof(
double)) == 0)
487 throw std::runtime_error(
"RGPOT: could not share a calculator result");
489 std::copy(buf.begin() + 2, buf.end(), F);
492 std::array<char, 512> msg{};
493 if (owner ==
impl_->group && !error.empty()) {
494 const auto n = std::min(error.size(), msg.size() - 1);
495 std::memcpy(msg.data(), error.data(), n);
497 if (::rgpot::shareFromCalculator(owner, msg.data(), msg.size()) == 0)
498 throw std::runtime_error(
"RGPOT: could not share a calculator error");
500 error.assign(msg.data());
501 return buf[0] == 1.0;
508 return impl_->nwchem->available();
510 return impl_->cpmd->available();
512 return impl_->metatomic->available();
514 return impl_->xtb->available();
519 double *F,
double *U,
const double *box)
const {
522 if (
const char *fail = std::getenv(
"RGPOT_FORCE_FAIL")) {
524 throw std::runtime_error(fail);
527 throw std::runtime_error(
"RGPotEngine::force called with N <= 0");
530 for (
long i = 0; i < N; ++i) {
531 const int ii =
static_cast<int>(i);
532 positions(ii, 0) = R[3 * i + 0];
533 positions(ii, 1) = R[3 * i + 1];
534 positions(ii, 2) = R[3 * i + 2];
536 std::vector<int> atmtypes(atomicNrs, atomicNrs + N);
537 const auto cell = box_from_row_major(box);
540 impl_->metatomic->force(N, R, atomicNrs, F, U,
nullptr, box);
544 impl_->xtb->force(N, R, atomicNrs, F, U,
nullptr, box);
549 std::tuple<double, AtomMatrix, double> result;
551 result = (*
impl_->nwchem)(positions, atmtypes, cell);
553 result = (*
impl_->cpmd)(positions, atmtypes, cell);
555 *U = std::get<0>(result);
556 const auto &forces = std::get<1>(result);
557 for (
long i = 0; i < N; ++i) {
558 const int ii =
static_cast<int>(i);
559 F[3 * i + 0] = forces(ii, 0);
560 F[3 * i + 1] = forces(ii, 1);
561 F[3 * i + 2] = forces(ii, 2);
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
RGPotEngine(const RGPotEngineOptions &opt)
void broadcastFromDriver(void *data, std::size_t bytes) const
Collective on MPI_COMM_WORLD: every rank leaves with world rank 0's bytes.
static bool mpiAbortRequested() noexcept
True once rgpot asked for MPI_Abort at exit in this process.
void armGroupedExit() const
After MPI_Finalize, _Exit.
int calculatorGroups() const noexcept
Number of calculator groups the MPI world is split into (1 when ranks_per_image is off) and the group...
void shutdownModule() noexcept
Calls cpmdc_finalize while the engine is still mapped. MPI stays up.
int calculatorWorld() const noexcept
Ranks in the MPI world the groups were bound on (1 without MPI).
int calculatorIndex() const noexcept
int worldRank() const noexcept
This process's rank in that world (0 without MPI).
void finalizeMpiAtExit() const
Registers MPI_Finalize at exit (once per process) when the world has more than one rank.
std::unique_ptr< Impl > impl_
bool shareResult(int owner, long N, double *F, double *U, bool ok, std::string &error) const
Collective on MPI_COMM_WORLD: every rank leaves with the energy and the 3N forces computed by the fir...
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, const double *box) const
::CPMDParams::Builder fillCpmdParams(::capnp::MallocMessageBuilder &msg, const RGPotEngineOptions &opt)
std::string extensions_directory
std::string engine_library
double xtb_electronic_temperature
std::string permanent_dir
bool torch_determinism_strict
double uncertainty_threshold
std::unique_ptr< rgpot::NWChemPot > nwchem
std::unique_ptr< rgpot::CPMDPot > cpmd
std::unique_ptr< MetatomicEngineLoader > metatomic
std::unique_ptr< XTBEngineLoader > xtb
double electronic_temperature