Loading...
Searching...
No Matches
RGPotEngine Class Reference

Opaque rgpot-backed engine (nwchemc / cpmdc / metatomic / xtb). More...

#include <RGPotEngine.h>

Classes

struct  Impl

Public Member Functions

 RGPotEngine (const RGPotEngineOptions &opt)
 ~RGPotEngine ()
 RGPotEngine (const RGPotEngine &)=delete
RGPotEngine & operator= (const RGPotEngine &)=delete
const std::string & backend () const noexcept
bool available () const
void force (long N, const double *R, const int *atomicNrs, double *F, double *U, const double *box) const
int calculatorGroups () const noexcept
 Number of calculator groups the MPI world is split into (1 when ranks_per_image is off) and the group this rank belongs to.
int calculatorIndex () const noexcept
int calculatorWorld () const noexcept
 Ranks in the MPI world the groups were bound on (1 without MPI).
int worldRank () const noexcept
 This process's rank in that world (0 without MPI).
void broadcastFromDriver (void *data, std::size_t bytes) const
 Collective on MPI_COMM_WORLD: every rank leaves with world rank 0's bytes.
void finalizeMpiAtExit () const
 Registers MPI_Finalize at exit (once per process) when the world has more than one rank.
void armGroupedExit () const
 After MPI_Finalize, _Exit.
void shutdownModule () noexcept
 Calls cpmdc_finalize while the engine is still mapped. MPI stays up.
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 first rank of group owner.

Static Public Member Functions

static bool mpiAbortRequested () noexcept
 True once rgpot asked for MPI_Abort at exit in this process.

Private Attributes

std::unique_ptr< Impl > impl_
std::string backend_

Detailed Description

Opaque rgpot-backed engine (nwchemc / cpmdc / metatomic / xtb).

Definition at line 46 of file RGPotEngine.h.

Constructor & Destructor Documentation

◆ RGPotEngine() [1/2]

RGPotEngine::RGPotEngine ( const RGPotEngineOptions & opt)
explicit

Definition at line 260 of file RGPotEngine.cpp.

261 : impl_(std::make_unique<Impl>()) {
262 backend_ = to_lower(opt.backend);
263 if (backend_ == "nwchem" || backend_ == "nwchemc" ||
264 backend_ == "nwchempot") {
265 backend_ = "nwchemc";
266 impl_->backend = Impl::Backend::Nwchemc;
267 ::capnp::MallocMessageBuilder msg;
268 auto params = msg.initRoot<::NWChemParams>();
269 params.setBasis(opt.basis);
270 params.setTheory(opt.theory);
271 params.setScfType(opt.scf_type);
272 params.setCharge(opt.charge);
273 params.setMultiplicity(opt.multiplicity);
274 if (!opt.engine_path.empty())
275 params.setEnginePath(opt.engine_path);
276 else if (!opt.engine_library.empty())
277 params.setEnginePath(opt.engine_library);
278 if (!opt.engine_root.empty())
279 params.setNwchemRoot(opt.engine_root);
280 if (!opt.title.empty())
281 params.setTitle(opt.title);
282 if (opt.memory_mb > 0)
283 params.setMemoryMb(static_cast<uint32_t>(opt.memory_mb));
284 if (!opt.scratch_dir.empty())
285 params.setScratchDir(opt.scratch_dir);
286 // DFT XC as inputBlocks when theory=dft and scfType is an XC label
287 std::string block = opt.input_block;
288 if (block.empty()) {
289 if (const char *env = std::getenv("RGPOT_NWCHEM_INPUT_BLOCK"))
290 block = env;
291 }
292 if (block.empty() && (opt.theory == "dft" || opt.theory == "DFT") &&
293 looks_like_dft_xc(opt.scf_type)) {
294 block = "dft\n xc " + opt.scf_type + "\n mult " +
295 std::to_string(opt.multiplicity) + "\nend";
296 } else if (block.empty() && looks_like_dft_xc(opt.theory)) {
297 block = "dft\n xc " + opt.theory + "\n mult " +
298 std::to_string(opt.multiplicity) + "\nend";
299 }
300 if (!block.empty()) {
301 auto blocks = params.initInputBlocks(1);
302 blocks.set(0, block);
303 }
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)");
309 } else if (backend_ == "cpmd" || backend_ == "cpmdc" ||
310 backend_ == "cpmdpot") {
311 backend_ = "cpmdc";
312 impl_->backend = Impl::Backend::Cpmdc;
313 std::string local_error;
314 try {
315 ::capnp::MallocMessageBuilder msg;
316 ::CPMDParams::Builder params = eon::fillCpmdParams(msg, opt);
317 impl_->cpmd = std::make_unique<rgpot::CPMDPot>(params.asReader());
318 pin_cpmd_library(opt.engine_path.empty() ? opt.engine_library
319 : opt.engine_path);
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();
326 }
327 // Several ranks: agree before MPI_Comm_split. One rank that cannot
328 // read params_path must not leave the others inside the split.
329 if (mpi_world_hint() <= 1 && !local_error.empty())
330 throw std::runtime_error(local_error);
331#if defined(__linux__) && defined(EON_RGPOT_MPI)
332 // rgpot reports MPI only once MPI_Init has run, and agree_construction
333 // is what initialises it, so the agreement keys on the launch size.
334 if (mpi_world_hint() > 1 && !agree_construction(local_error)) {
336 throw std::runtime_error(
337 local_error.empty()
338 ? std::string(
339 "RGPOT(cpmdc): a rank failed before the calculator split")
340 : local_error);
341 }
342#endif
343 if (!local_error.empty())
344 throw std::runtime_error(local_error);
345 if (::rgpot::calculatorsUseMpi()) {
346 // Collective on MPI_COMM_WORLD, before the first force: the engine
347 // installs the group communicator ahead of CPMD's mp_start.
348 // ranks_per_image = 0 is one calculator on the whole world.
349 const rgpot::CalculatorGroup g =
350 ::rgpot::bindCalculators(opt.ranks_per_image);
351 ::rgpot::finalizeMpiAtExit();
352 if (mpi_world_hint() > 1)
354 if (g.index < 0)
355 throw std::runtime_error(
356 "RGPOT(cpmdc): ranks_per_image=" +
357 std::to_string(opt.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();
362 } else if (opt.ranks_per_image > 0) {
363 throw std::runtime_error(
364 "RGPOT(cpmdc): ranks_per_image needs rgpot built with MPI "
365 "(-Drgpot:with_mpi=enabled)");
366 }
367 } else if (backend_ == "metatomic" || backend_ == "mta" ||
368 backend_ == "metatomicpot") {
369 backend_ = "metatomic";
371 MetatomicEngineOptions mopt;
372 mopt.model_path = opt.model_path;
373 mopt.device = opt.device;
374 mopt.length_unit = opt.length_unit;
378 mopt.engine_path =
379 !opt.engine_path.empty() ? opt.engine_path : opt.engine_library;
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)");
386 } else if (backend_ == "xtb" || backend_ == "xtbpot" || backend_ == "gfn" ||
387 backend_ == "gfnxtb") {
388 backend_ = "xtb";
389 impl_->backend = Impl::Backend::Xtb;
390 XTBEngineOptions xopt;
391 xopt.method = xtb_method_from_paramset(opt.xtb_paramset);
392 xopt.accuracy = opt.xtb_accuracy;
395 xopt.charge = opt.xtb_charge;
396 xopt.uhf = opt.xtb_uhf;
397 xopt.engine_path =
398 !opt.engine_path.empty() ? opt.engine_path : opt.engine_library;
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)");
404 } else {
405 throw std::runtime_error("RGPOT: unknown backend '" + opt.backend +
406 "' (expected nwchemc, cpmdc, metatomic, or xtb)");
407 }
408}
void armGroupedExit() const
After MPI_Finalize, _Exit.
std::unique_ptr< Impl > impl_
Definition RGPotEngine.h:92
std::string backend_
Definition RGPotEngine.h:93
::CPMDParams::Builder fillCpmdParams(::capnp::MallocMessageBuilder &msg, const RGPotEngineOptions &opt)
std::string scf_type
Definition RGPotEngine.h:11
std::string title
Definition RGPotEngine.h:19
std::string xtb_paramset
Definition RGPotEngine.h:37
std::string basis
Definition RGPotEngine.h:9
std::string scratch_dir
Definition RGPotEngine.h:21
std::string engine_path
Definition RGPotEngine.h:16
std::string backend
Definition RGPotEngine.h:8
std::string extensions_directory
Definition RGPotEngine.h:32
std::string engine_library
Definition RGPotEngine.h:17
double xtb_electronic_temperature
Definition RGPotEngine.h:39
std::string length_unit
Definition RGPotEngine.h:31
bool torch_determinism_strict
Definition RGPotEngine.h:35
std::string device
Definition RGPotEngine.h:30
std::string model_path
Definition RGPotEngine.h:29
std::string engine_root
Definition RGPotEngine.h:18
double uncertainty_threshold
Definition RGPotEngine.h:34
std::string theory
Definition RGPotEngine.h:10
std::string input_block
Definition RGPotEngine.h:23
std::string engine_path
double electronic_temperature

◆ ~RGPotEngine()

RGPotEngine::~RGPotEngine ( )
default

◆ RGPotEngine() [2/2]

RGPotEngine::RGPotEngine ( const RGPotEngine & )
delete

Member Function Documentation

◆ armGroupedExit()

void RGPotEngine::armGroupedExit ( ) const

After MPI_Finalize, _Exit.

Library destructors do not run on a finalized MPI world. When rgpot requested an abort at exit (a failed engine call), MPI_Abort replaces MPI_Finalize. No-op unless this process was started as one of several ranks.

Definition at line 440 of file RGPotEngine.cpp.

440 {
441#if defined(__linux__) && defined(EON_RGPOT_MPI)
442 if (mpi_world_hint() <= 1)
443 return;
444 static std::once_flag once;
445 std::call_once(once, [] { ::on_exit(eon_rgpot_hard_exit, nullptr); });
446#else
447 (void)this;
448#endif
449}

◆ available()

bool RGPotEngine::available ( ) const
nodiscard

Definition at line 504 of file RGPotEngine.cpp.

504 {
505 if (!impl_)
506 return false;
507 if (impl_->backend == Impl::Backend::Nwchemc && impl_->nwchem)
508 return impl_->nwchem->available();
509 if (impl_->backend == Impl::Backend::Cpmdc && impl_->cpmd)
510 return impl_->cpmd->available();
511 if (impl_->backend == Impl::Backend::Metatomic && impl_->metatomic)
512 return impl_->metatomic->available();
513 if (impl_->backend == Impl::Backend::Xtb && impl_->xtb)
514 return impl_->xtb->available();
515 return false;
516}

◆ backend()

const std::string & RGPotEngine::backend ( ) const
inlinenodiscardnoexcept

Definition at line 53 of file RGPotEngine.h.

53{ return backend_; }

◆ broadcastFromDriver()

void RGPotEngine::broadcastFromDriver ( void * data,
std::size_t bytes ) const

Collective on MPI_COMM_WORLD: every rank leaves with world rank 0's bytes.

A no-op on one rank.

Definition at line 463 of file RGPotEngine.cpp.

463 {
464 if (!impl_ || impl_->world <= 1 || bytes == 0)
465 return;
466 // World rank 0 is the first rank of calculator 0.
467 if (::rgpot::shareFromCalculator(0, data, bytes) == 0)
468 throw std::runtime_error("RGPOT: could not broadcast from the driver rank");
469}

◆ calculatorGroups()

int RGPotEngine::calculatorGroups ( ) const
nodiscardnoexcept

Number of calculator groups the MPI world is split into (1 when ranks_per_image is off) and the group this rank belongs to.

Definition at line 412 of file RGPotEngine.cpp.

412 {
413 return impl_ ? impl_->groups : 1;
414}

◆ calculatorIndex()

int RGPotEngine::calculatorIndex ( ) const
nodiscardnoexcept

Definition at line 416 of file RGPotEngine.cpp.

416 {
417 return impl_ ? impl_->group : 0;
418}

◆ calculatorWorld()

int RGPotEngine::calculatorWorld ( ) const
nodiscardnoexcept

Ranks in the MPI world the groups were bound on (1 without MPI).

Definition at line 420 of file RGPotEngine.cpp.

420 {
421 return impl_ ? impl_->world : 1;
422}

◆ finalizeMpiAtExit()

void RGPotEngine::finalizeMpiAtExit ( ) const

Registers MPI_Finalize at exit (once per process) when the world has more than one rank.

Definition at line 431 of file RGPotEngine.cpp.

431 {
432 if (impl_ && impl_->world > 1)
433 ::rgpot::finalizeMpiAtExit();
434}

◆ force()

void RGPotEngine::force ( long N,
const double * R,
const int * atomicNrs,
double * F,
double * U,
const double * box ) const

Definition at line 518 of file RGPotEngine.cpp.

519 {
520 // Set on one rank by the multi-rank failure test. The text is the
521 // engine error that rank has to deliver to rank 0.
522 if (const char *fail = std::getenv("RGPOT_FORCE_FAIL")) {
523 if (fail[0] != '\0')
524 throw std::runtime_error(fail);
525 }
526 if (N <= 0)
527 throw std::runtime_error("RGPotEngine::force called with N <= 0");
528
529 AtomMatrix positions(static_cast<int>(N), 3);
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];
535 }
536 std::vector<int> atmtypes(atomicNrs, atomicNrs + N);
537 const auto cell = box_from_row_major(box);
538
539 if (impl_->backend == Impl::Backend::Metatomic) {
540 impl_->metatomic->force(N, R, atomicNrs, F, U, nullptr, box);
541 return;
542 }
543 if (impl_->backend == Impl::Backend::Xtb) {
544 impl_->xtb->force(N, R, atomicNrs, F, U, nullptr, box);
545 return;
546 }
547
548 // rgpot >= 2.5.0: operator() returns (energy, forces, variance).
549 std::tuple<double, AtomMatrix, double> result;
550 if (impl_->backend == Impl::Backend::Nwchemc)
551 result = (*impl_->nwchem)(positions, atmtypes, cell);
552 else
553 result = (*impl_->cpmd)(positions, atmtypes, cell);
554
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);
562 }
563}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37

◆ mpiAbortRequested()

bool RGPotEngine::mpiAbortRequested ( )
staticnodiscardnoexcept

True once rgpot asked for MPI_Abort at exit in this process.

Definition at line 436 of file RGPotEngine.cpp.

436 {
437 return ::rgpot::mpiAbortRequested();
438}

◆ operator=()

RGPotEngine & RGPotEngine::operator= ( const RGPotEngine & )
delete

◆ shareResult()

bool RGPotEngine::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 first rank of group owner.

Only CPMD's parent rank holds the true forces, so this also keeps the ranks of one group on the same step. ok is the owner's status; the return value is the owner's status on every rank. error is the owner's engine message on every rank.

Definition at line 471 of file RGPotEngine.cpp.

472 {
473 if (!impl_ || impl_->world <= 1) {
474 (void)error;
475 return ok;
476 }
477 // Slot 0 carries the owner's status, so a failed evaluation reaches
478 // every rank through the same broadcast as a good one.
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;
482 buf[1] = *U;
483 std::copy(F, F + 3 * N, buf.begin() + 2);
484 }
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");
488 *U = buf[1];
489 std::copy(buf.begin() + 2, buf.end(), F);
490 // The owner's engine text travels with the status. Other ranks hold an
491 // empty string until this broadcast.
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);
496 }
497 if (::rgpot::shareFromCalculator(owner, msg.data(), msg.size()) == 0)
498 throw std::runtime_error("RGPOT: could not share a calculator error");
499 if (msg[0] != '\0')
500 error.assign(msg.data());
501 return buf[0] == 1.0;
502}
std::string error()
Definition DynLib.h:82

◆ shutdownModule()

void RGPotEngine::shutdownModule ( )
noexcept

Calls cpmdc_finalize while the engine is still mapped. MPI stays up.

Definition at line 451 of file RGPotEngine.cpp.

451 {
452 if (!impl_ || impl_->module_down)
453 return;
454 impl_->module_down = true;
455#if !defined(_WIN32)
456 if (auto *fin =
457 reinterpret_cast<void (*)()>(dlsym(RTLD_DEFAULT, "cpmdc_finalize")))
458 fin();
459 pin_cpmd_library({});
460#endif
461}

◆ worldRank()

int RGPotEngine::worldRank ( ) const
nodiscardnoexcept

This process's rank in that world (0 without MPI).

Definition at line 424 of file RGPotEngine.cpp.

424 {
425 if (!impl_ || impl_->world <= 1)
426 return 0;
427 const rgpot::CalculatorGroup &g = ::rgpot::thisCalculator();
428 return g.index * g.ranks + g.rank_in_group;
429}

Member Data Documentation

◆ backend_

std::string RGPotEngine::backend_
private

Definition at line 93 of file RGPotEngine.h.

◆ impl_

std::unique_ptr<Impl> RGPotEngine::impl_
private

Definition at line 92 of file RGPotEngine.h.


The documentation for this class was generated from the following files: