Loading...
Searching...
No Matches
RgpotPot.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3*/
5#include "eon/Parameters.h"
6#include "eon/PotRegistry.h"
8
9#include <algorithm>
10#include <cctype>
11#include <cstdint>
12#include <cstdlib>
13#include <iostream>
14#include <ranges>
15#include <string>
16#include <vector>
17
19 : eonc::Potential(eonc::PotType::RGPOT, p) {
21 const auto &o = p.rgpot_options();
22 opt.backend = o.backend;
23 opt.basis = o.basis;
24 opt.theory = o.theory;
25 opt.scf_type = o.scf_type;
26 opt.functional = o.functional;
27 opt.cutoff_ry = o.cutoff_ry;
28 opt.charge = o.charge;
29 opt.multiplicity = o.multiplicity;
30 opt.engine_path = o.engine_path;
31 opt.engine_library = o.engine_library;
32 opt.engine_root = o.engine_root;
33 opt.title = o.title;
34 opt.memory_mb = o.memory_mb;
35 opt.scratch_dir = o.scratch_dir;
36 opt.input_block = o.input_block;
37 opt.permanent_dir = o.permanent_dir;
38 opt.params_path = o.params_path;
39 opt.ranks_per_image = o.ranks_per_image;
40 opt.model_path = o.model_path;
41 opt.device = o.device;
42 opt.length_unit = o.length_unit;
43 opt.extensions_directory = o.extensions_directory;
44 opt.check_consistency = o.check_consistency;
45 opt.uncertainty_threshold = o.uncertainty_threshold;
46 opt.torch_determinism_strict = o.torch_determinism_strict;
47 opt.xtb_paramset = o.xtb_paramset;
48 opt.xtb_accuracy = o.xtb_accuracy;
49 opt.xtb_electronic_temperature = o.xtb_electronic_temperature;
50 opt.xtb_max_iterations = o.xtb_max_iterations;
51 opt.xtb_charge = o.xtb_charge;
52 opt.xtb_uhf = o.xtb_uhf;
53
54 // Env overrides (CI / benchmarks)
55 if (const char *e = std::getenv("RGPOT_BACKEND"))
56 opt.backend = e;
57 if (const char *e = std::getenv("RGPOT_NWCHEM_BASIS"))
58 opt.basis = e;
59 if (const char *e = std::getenv("RGPOT_NWCHEM_THEORY"))
60 opt.theory = e;
61 if (const char *e = std::getenv("RGPOT_NWCHEM_SCF_TYPE"))
62 opt.scf_type = e;
63 if (const char *e = std::getenv("RGPOT_PARAMS_PATH"))
64 opt.params_path = e;
65 // Engine-path env overrides are backend-scoped: NWCHEMC_LIBRARY must not
66 // leak into a cpmdc configure (CPMDPot resolves CPMDC_LIBRARY itself).
67 std::string backend_lc = opt.backend;
68 std::ranges::transform(backend_lc, backend_lc.begin(),
69 [](unsigned char c) { return std::tolower(c); });
70 if (backend_lc.rfind("nwchem", 0) == 0) {
71 if (const char *e = std::getenv("NWCHEMC_LIBRARY"))
72 opt.engine_path = e;
73 else if (const char *e = std::getenv("RGPOT_NWCHEMC_ENGINE"))
74 opt.engine_path = e;
75 else if (const char *e = std::getenv("RGPOT_NWCHEM_ENGINE"))
76 opt.engine_path = e;
77 } else if (backend_lc.rfind("cpmd", 0) == 0) {
78 if (const char *e = std::getenv("CPMDC_LIBRARY"))
79 opt.engine_path = e;
80 else if (const char *e = std::getenv("RGPOT_CPMDC_ENGINE"))
81 opt.engine_path = e;
82 } else if (backend_lc.rfind("meta", 0) == 0 || backend_lc == "mta") {
83 if (const char *e = std::getenv("RGPOT_METATOMIC_ENGINE"))
84 opt.engine_path = e;
85 else if (const char *e = std::getenv("METATOMIC_ENGINE"))
86 opt.engine_path = e;
87 if (const char *e = std::getenv("RGPOT_METATOMIC_MODEL"))
88 opt.model_path = e;
89 } else if (backend_lc == "xtb" || backend_lc == "xtbpot" ||
90 backend_lc == "gfn" || backend_lc == "gfnxtb") {
91 if (const char *e = std::getenv("RGPOT_XTB_ENGINE"))
92 opt.engine_path = e;
93 else if (const char *e = std::getenv("XTB_ENGINE"))
94 opt.engine_path = e;
95 if (opt.xtb_paramset.empty() || opt.xtb_paramset == "GFN2xTB") {
96 if (!p.xtb_options().paramset.empty())
98 }
99 }
100
101 // Dual-read [Metatomic] when RGPOT backend is metatomic
102 if ((backend_lc.rfind("meta", 0) == 0 || backend_lc == "mta") &&
103 opt.model_path.empty())
105 if ((backend_lc.rfind("meta", 0) == 0 || backend_lc == "mta") &&
106 opt.device == "cpu" && !p.metatomic_options().device.empty())
108
109 impl_ = std::make_unique<RGPotEngine>(opt);
110 backend_ = impl_->backend();
111 driver_ = impl_->worldRank() == 0;
112 std::cout
113 << "RgpotPot: in-process rgpot backend=" << backend_
114 << " (dlopen: libnwchemc/libcpmdc/libmetatomic_engine/libxtb_engine)"
115 << std::endl;
116 // Finalize is registered first. The grouped-exit handler is next, and
117 // the stop handler is last, so exit runs stop, then Finalize, then _Exit.
118 impl_->finalizeMpiAtExit();
119 impl_->armGroupedExit();
121 if (!driver_)
122 serveWorker();
123}
124
126 const bool grouped = impl_ && impl_->calculatorWorld() > 1;
127 stopAndDrop();
128 // ~CPMDPot dlcloses libcpmdc. On several ranks that runs Fortran
129 // destructors while another rank may already be in MPI_Finalize.
130 // The process reclaims the engine at _Exit.
131 if (grouped)
132 (void)impl_.release();
133}
134
135bool RgpotPot::engineAvailable() const { return impl_ && impl_->available(); }
136
137namespace {
138// Request header broadcast from the driver: kind, atoms, systems.
139// kDown is the second broadcast: every rank has dropped CPMD, and
140// workers may enter MPI_Finalize.
141enum : std::int64_t { kStop = 0, kSingle = 1, kBatch = 2, kDown = 3 };
142
143// The driver's live potential, so an exit that skips the destructor still
144// releases the workers before MPI_Finalize.
145RgpotPot *g_driver = nullptr;
146} // namespace
147
149 if (!impl_ || !driver_ || stopped_ || impl_->calculatorWorld() <= 1)
150 return;
151 std::int64_t hdr[3] = {kStop, 0, 0};
152 impl_->broadcastFromDriver(hdr, sizeof(hdr));
153 stopped_ = true;
154 if (g_driver == this)
155 g_driver = nullptr;
156}
157
159 if (!impl_)
160 return;
161 const bool grouped = driver_ && impl_->calculatorWorld() > 1;
162 if (grouped && !stopped_)
163 sendStop();
164 if (!dropped_) {
165 impl_->shutdownModule();
166 dropped_ = true;
167 }
168 if (grouped && !acked_) {
169 std::int64_t hdr[3] = {kDown, 0, 0};
170 impl_->broadcastFromDriver(hdr, sizeof(hdr));
171 acked_ = true;
172 }
173}
174
176 if (g_driver || !impl_ || impl_->calculatorWorld() <= 1)
177 return;
178 // Runs before the MPI_Finalize handler registered in the constructor.
179 // After a failed engine call the workers can sit in a collective that
180 // the stop broadcast never meets; the grouped exit handler aborts the
181 // world instead.
182 g_driver = this;
183 std::atexit([] {
184 if (g_driver && !RGPotEngine::mpiAbortRequested())
185 g_driver->stopAndDrop();
186 });
187}
188
189namespace {
190// Runs one evaluation and keeps its error instead of throwing, so the
191// rank still joins every broadcast that follows.
192bool try_force(const RGPotEngine &engine, long N, const double *R,
193 const int *atomicNrs, double *F, double *U, const double *box,
194 std::string &error) {
195 try {
196 engine.force(N, R, atomicNrs, F, U, box);
197 return true;
198 } catch (const std::exception &ex) {
199 error = ex.what();
200 return false;
201 }
202}
203
204[[noreturn]] void raise_failure(long system, int owner,
205 const std::string &error) {
206 std::string msg = "RGPOT: calculator " + std::to_string(owner) +
207 " failed on system " + std::to_string(system);
208 if (!error.empty())
209 msg += ": " + error;
210 throw std::runtime_error(msg);
211}
212} // namespace
213
214void RgpotPot::computeSingle(long N, const double *R, const int *atomicNrs,
215 double *F, double *U, const double *box) {
216 // Group 0 computes; its first rank's result reaches every rank.
217 std::string error;
218 bool ok = true;
219 if (impl_->calculatorIndex() == 0)
220 ok = try_force(*impl_, N, R, atomicNrs, F, U, box, error);
221 if (!impl_->shareResult(0, N, F, U, ok, error))
222 raise_failure(0, 0, error);
223}
224
225void RgpotPot::computeBatch(long nSystems, long nAtoms,
226 const double *const *positions,
227 const int *const *atomicNrs, double *const *forces,
228 double *energies, const double *const *boxes,
229 const std::int64_t *owners) {
230 const int groups = impl_->calculatorGroups();
231 const int mine = impl_->calculatorIndex();
232 auto ownerOf = [&](long j) {
233 const std::int64_t id = owners && owners[j] >= 0 ? owners[j] : j;
234 return static_cast<int>(id % groups);
235 };
236 if (impl_->calculatorWorld() <= 1) {
237 for (long j = 0; j < nSystems; j++)
238 impl_->force(nAtoms, positions[j], atomicNrs[j], forces[j], &energies[j],
239 boxes[j]);
240 return;
241 }
242 std::vector<char> ok(static_cast<size_t>(nSystems), 1);
243 std::vector<std::string> errors(static_cast<size_t>(nSystems));
244 for (long j = 0; j < nSystems; j++) {
245 if (ownerOf(j) == mine)
246 ok[static_cast<size_t>(j)] =
247 try_force(*impl_, nAtoms, positions[j], atomicNrs[j], forces[j],
248 &energies[j], boxes[j], errors[static_cast<size_t>(j)]);
249 }
250 // Every share runs before any rank raises, so all ranks leave together.
251 long failed = -1;
252 for (long j = 0; j < nSystems; j++) {
253 const int owner = ownerOf(j);
254 if (!impl_->shareResult(owner, nAtoms, forces[j], &energies[j],
255 ok[static_cast<size_t>(j)] != 0,
256 errors[static_cast<size_t>(j)]) &&
257 failed < 0)
258 failed = j;
259 }
260 if (failed >= 0)
261 raise_failure(failed, ownerOf(failed), errors[static_cast<size_t>(failed)]);
262}
263
265 for (;;) {
266 std::int64_t hdr[3] = {kStop, 0, 0};
267 impl_->broadcastFromDriver(hdr, sizeof(hdr));
268 if (hdr[0] == kStop) {
269 // Drop CPMD, wait until the driver has dropped it too, then exit 0.
270 // MPI_Finalize is collective and runs from the exit handler.
271 if (!dropped_) {
272 impl_->shutdownModule();
273 dropped_ = true;
274 }
275 std::int64_t ack[3] = {kDown, 0, 0};
276 impl_->broadcastFromDriver(ack, sizeof(ack));
277 std::exit(0);
278 }
279 const long n = static_cast<long>(hdr[1]);
280 const long m = hdr[0] == kBatch ? static_cast<long>(hdr[2]) : 1;
281 std::vector<double> R(static_cast<size_t>(3 * n * m));
282 std::vector<int> Z(static_cast<size_t>(n * m));
283 std::vector<double> box(static_cast<size_t>(9 * m));
284 impl_->broadcastFromDriver(R.data(), R.size() * sizeof(double));
285 impl_->broadcastFromDriver(Z.data(), Z.size() * sizeof(int));
286 impl_->broadcastFromDriver(box.data(), box.size() * sizeof(double));
287 std::vector<std::int64_t> ids(static_cast<size_t>(m), -1);
288 if (hdr[0] == kBatch)
289 impl_->broadcastFromDriver(ids.data(), ids.size() * sizeof(std::int64_t));
290 std::vector<double> F(R.size()), U(static_cast<size_t>(m));
291 // A failed request raised on the driver too; the driver decides
292 // whether the job goes on, so a worker keeps serving.
293 if (hdr[0] == kSingle) {
294 try {
295 computeSingle(n, R.data(), Z.data(), F.data(), U.data(), box.data());
296 } catch (const std::runtime_error &) {
297 }
298 continue;
299 }
300 std::vector<const double *> pos(static_cast<size_t>(m)), bx(pos.size());
301 std::vector<const int *> nrs(pos.size());
302 std::vector<double *> frc(pos.size());
303 for (long j = 0; j < m; j++) {
304 pos[static_cast<size_t>(j)] = R.data() + 3 * n * j;
305 nrs[static_cast<size_t>(j)] = Z.data() + n * j;
306 frc[static_cast<size_t>(j)] = F.data() + 3 * n * j;
307 bx[static_cast<size_t>(j)] = box.data() + 9 * j;
308 }
309 try {
310 computeBatch(m, n, pos.data(), nrs.data(), frc.data(), U.data(),
311 bx.data(), ids.data());
312 } catch (const std::runtime_error &) {
313 }
314 }
315}
316
317void RgpotPot::force(long N, const double *R, const int *atomicNrs, double *F,
318 double *U, double *variance, const double *box) {
319 if (variance)
320 *variance = 0.0;
321 if (impl_->calculatorWorld() <= 1) {
322 impl_->force(N, R, atomicNrs, F, U, box);
323 return;
324 }
325 std::int64_t hdr[3] = {kSingle, N, 1};
326 impl_->broadcastFromDriver(hdr, sizeof(hdr));
327 impl_->broadcastFromDriver(const_cast<double *>(R), 3 * N * sizeof(double));
328 impl_->broadcastFromDriver(const_cast<int *>(atomicNrs), N * sizeof(int));
329 impl_->broadcastFromDriver(const_cast<double *>(box), 9 * sizeof(double));
330 // Before the evaluation, so a failure that ends the job still stops
331 // the workers.
333 computeSingle(N, R, atomicNrs, F, U, box);
334}
335
337 return impl_ && impl_->calculatorGroups() > 1;
338}
339
340void RgpotPot::forceBatch(long nSystems, long nAtoms,
341 const double *const *positions,
342 const int *const *atomicNrs, double *const *forces,
343 double *energies, double *variances,
344 const double *const *boxes) {
345 forceBatchOwned(nSystems, nAtoms, positions, atomicNrs, forces, energies,
346 variances, boxes, nullptr);
347}
348
349void RgpotPot::forceBatchOwned(long nSystems, long nAtoms,
350 const double *const *positions,
351 const int *const *atomicNrs,
352 double *const *forces, double *energies,
353 double *variances, const double *const *boxes,
354 const long *owners) {
355 std::vector<std::int64_t> ids(static_cast<size_t>(nSystems), -1);
356 if (owners) {
357 for (long j = 0; j < nSystems; j++)
358 ids[static_cast<size_t>(j)] = owners[j];
359 }
360 if (impl_->calculatorWorld() > 1) {
361 std::int64_t hdr[3] = {kBatch, nAtoms, nSystems};
362 impl_->broadcastFromDriver(hdr, sizeof(hdr));
363 std::vector<double> R(static_cast<size_t>(3 * nAtoms * nSystems));
364 std::vector<int> Z(static_cast<size_t>(nAtoms * nSystems));
365 std::vector<double> box(static_cast<size_t>(9 * nSystems));
366 for (long j = 0; j < nSystems; j++) {
367 std::copy(positions[j], positions[j] + 3 * nAtoms,
368 R.begin() + 3 * nAtoms * j);
369 std::copy(atomicNrs[j], atomicNrs[j] + nAtoms, Z.begin() + nAtoms * j);
370 std::copy(boxes[j], boxes[j] + 9, box.begin() + 9 * j);
371 }
372 impl_->broadcastFromDriver(R.data(), R.size() * sizeof(double));
373 impl_->broadcastFromDriver(Z.data(), Z.size() * sizeof(int));
374 impl_->broadcastFromDriver(box.data(), box.size() * sizeof(double));
375 impl_->broadcastFromDriver(ids.data(), ids.size() * sizeof(std::int64_t));
376 }
378 computeBatch(nSystems, nAtoms, positions, atomicNrs, forces, energies, boxes,
379 ids.data());
380 for (long j = 0; j < nSystems; j++) {
381 if (variances)
382 variances[j] = 0.0;
385 }
386}
Opaque rgpot-backed engine (nwchemc / cpmdc / metatomic / xtb).
Definition RGPotEngine.h:46
static bool mpiAbortRequested() noexcept
True once rgpot asked for MPI_Abort at exit in this process.
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, const double *box) const
Potential backed by rgpot NWChemPot / CPMDPot (in-process dlopen of libnwchemc / libcpmdc).
Definition RgpotPot.h:25
RgpotPot(const eonc::Parameters &p)
Definition RgpotPot.cpp:18
bool engineAvailable() const
Definition RgpotPot.cpp:135
std::string backend_
Definition RgpotPot.h:77
void computeSingle(long N, const double *R, const int *atomicNrs, double *F, double *U, const double *box)
Definition RgpotPot.cpp:214
std::unique_ptr< RGPotEngine > impl_
Definition RgpotPot.h:76
void forceBatchOwned(long nSystems, long nAtoms, const double *const *positions, const int *const *atomicNrs, double *const *forces, double *energies, double *variances, const double *const *boxes, const long *owners) override
System j runs on group owners[j] mod G when owners is given (an owner below zero falls back to j),...
Definition RgpotPot.cpp:349
void forceBatch(long nSystems, long nAtoms, const double *const *positions, const int *const *atomicNrs, double *const *forces, double *energies, double *variances, const double *const *boxes) override
Definition RgpotPot.cpp:340
bool stopped_
Definition RgpotPot.h:79
void computeBatch(long nSystems, long nAtoms, const double *const *positions, const int *const *atomicNrs, double *const *forces, double *energies, const double *const *boxes, const std::int64_t *owners)
Definition RgpotPot.cpp:225
bool dropped_
Definition RgpotPot.h:80
bool acked_
Definition RgpotPot.h:81
bool driver_
Definition RgpotPot.h:78
~RgpotPot() override
Definition RgpotPot.cpp:125
void stopAndDrop()
Definition RgpotPot.cpp:158
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
Definition RgpotPot.cpp:317
void serveWorker()
Definition RgpotPot.cpp:264
bool supportsBatchEvaluation() const noexcept override
With cpmdc calculator groups ([RgpotPot] ranks_per_image), a batch is spread over the groups: system ...
Definition RgpotPot.cpp:336
void sendStop()
Definition RgpotPot.cpp:148
void releaseWorkersAtExit()
Definition RgpotPot.cpp:175
const metatomic_options_t & metatomic_options() const
const xtb_options_t & xtb_options() const
const rgpot_options_t & rgpot_options() const
void on_force_call(PotType t) noexcept override
static PotRegistry & get() noexcept
Process-lifetime singleton.
std::atomic< size_t > forceCallCounter
Definition Potential.h:53
PotType ptype
Definition Potential.h:44
Potential(PotType a_ptype)
Production default: construction-scope registry, else PotRegistry::get().
std::string error()
Definition DynLib.h:82
RAII resource manager for the ARTn C library with global synchronization.
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 params_path
Definition RGPotEngine.h:26
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 permanent_dir
Definition RGPotEngine.h:24
std::string functional
Definition RGPotEngine.h:12
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