Loading...
Searching...
No Matches
LAMMPSPot.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
13#include "eon/EonLogger.h"
14#include "eon/Parameters.h"
15#include "eon/fpe_handler.h"
17
18#include <atomic>
19#include <cmath>
20#include <cstring>
21#include <filesystem>
22#include <format>
23#include <fstream>
24#include <map>
25#include <stdexcept>
26#include <string>
27
28#if !defined(EONMPI) && !defined(IS_WINDOWS)
29#include <cerrno>
30#include <cstdlib>
31#include <vector>
32
33#include <csignal>
34#include <poll.h>
35#include <sys/wait.h>
36#include <unistd.h>
37#endif
38
39#ifdef EONMPI
40#define LAMMPS_LIB_MPI
41#include "eon/ParametersMpi.h"
42#endif
43
45 : LAMMPSPot(p, eonc::LammpsLoader::instance(), true) {}
46
48 : LAMMPSPot(p, loader, false) {}
49
51 bool isolate_worker)
52 : eonc::Potential(p),
53 loader_{loader},
54 lammpsThr{p.potential_options().LAMMPSThreads},
55 lammpsLogging_{p.potential_options().LAMMPSLogging}
56#ifdef EONMPI
57 ,
58 mpiComm{eonc::getMpiClientComm(p)}
59#endif
60{
61 // Fail fast if LAMMPS library not available
62 loader_.require_loaded();
63 if (lammpsLogging_) {
64 static std::atomic<int> ids{0};
65 lammpsLogIndex_ = ids.fetch_add(1);
67 "client_lammps-" + std::to_string(lammpsLogIndex_) + ".log";
68 }
69#if !defined(EONMPI) && !defined(IS_WINDOWS)
70 if (!isolate_worker) {
71 return;
72 }
73 // Fork the worker NOW, at construction, before this process ever opens a
74 // LAMMPS instance (and thus before liblammps initialises MPI). Open MPI
75 // does not support using MPI in a process that called MPI_Init before fork,
76 // so the worker must be spawned from a still-MPI-clean parent. Every
77 // LAMMPSPot -- endpoints and per-image alike -- runs its LAMMPS in its own
78 // child process, so the parent never initialises MPI at all.
80#else
81 (void)isolate_worker;
82#endif
83}
84
86
87void LAMMPSPot::setFixedMask(long nAtoms, const double *isFixed) {
88 std::unique_lock<std::mutex> lock(maskMutex_, std::defer_lock);
89 if (!workerChild_) {
90 lock.lock();
91 }
92 if (nAtoms <= 0 || isFixed == nullptr) {
93 fixedMask_.clear();
94 maskN_ = 0;
95 return;
96 }
97 fixedMask_.assign(isFixed, isFixed + 3 * nAtoms);
98 maskN_ = nAtoms;
99}
100
102 std::vector<double> mask;
103 {
104 // The worker child inherits this mutex across fork. Only the parent locks.
105 std::unique_lock<std::mutex> lock(maskMutex_, std::defer_lock);
106 if (!workerChild_) {
107 lock.lock();
108 }
109 if (LAMMPSObj == nullptr || maskN_ != N || fixedMask_.empty()) {
110 return;
111 }
112 mask = fixedMask_;
113 }
114 static constexpr const char *kUnfix[] = {"unfix eon_fx", "unfix eon_fy",
115 "unfix eon_fz", "unfix eon_freeze"};
116 static constexpr const char *kUngroup[] = {
117 "group eon_fx delete", "group eon_fy delete", "group eon_fz delete",
118 "group eon_frozen delete"};
119 for (const char *cmd : kUnfix) {
120 try {
121 lammpsCommand(cmd);
122 } catch (...) {
123 }
124 }
125 for (const char *cmd : kUngroup) {
126 try {
127 lammpsCommand(cmd);
128 } catch (...) {
129 }
130 }
131 // Matter stores per-axis isFixed. A z-only freeze must not leave LAMMPS
132 // free to move that atom in z.
133 std::string ids[3];
134 for (long i = 0; i < N; ++i) {
135 for (int ax = 0; ax < 3; ++ax) {
136 if (mask[static_cast<size_t>(3 * i + ax)] >= 0.5) {
137 ids[ax] += std::format("{} ", i + 1);
138 }
139 }
140 }
141 static constexpr const char *kGroup[] = {"eon_fx", "eon_fy", "eon_fz"};
142 static constexpr const char *kFix[] = {
143 "fix eon_fx eon_fx setforce 0.0 NULL NULL",
144 "fix eon_fy eon_fy setforce NULL 0.0 NULL",
145 "fix eon_fz eon_fz setforce NULL NULL 0.0"};
146 for (int ax = 0; ax < 3; ++ax) {
147 if (ids[ax].empty()) {
148 continue;
149 }
151 ("group " + std::string(kGroup[ax]) + " id " + ids[ax]).c_str());
152 lammpsCommand(kFix[ax]);
153 }
154}
155
157#if !defined(EONMPI) && !defined(IS_WINDOWS)
158 stopWorker();
159#endif
160 if (LAMMPSObj != nullptr) {
162 loader_.close(LAMMPSObj);
163 LAMMPSObj = nullptr;
164 }
165}
166
167#if !defined(EONMPI) && !defined(IS_WINDOWS)
168// ---------------------------------------------------------------------------
169// Process-per-image worker plumbing (POSIX only)
170// ---------------------------------------------------------------------------
171namespace {
172// Report a geometry the worker could not evaluate as an impassable wall.
173//
174// Every failure path here used to throw, and nothing between the potential
175// call and main catches it, so the client terminated. That loses the whole
176// job, including searches that had already converged: a copper V/SIA search
177// reached a saddle at 0.082 eV with a force of 0.027 eV/A and a curvature of
178// -0.47, then died on the next evaluation before the result was written.
179//
180// A large finite energy with zeroed forces reads to the optimiser as a wall,
181// so it backs out of the step and the search abandons this configuration and
182// carries on. Callers stop the worker first; ensureWorker respawns it on the
183// next evaluation.
184void rejectGeometry(double *U, double *F, long N) {
185 *U = 1.0e6;
186 // Zero forces would be read as convergence: an optimiser judges a point
187 // converged on force magnitude alone, so a rejected geometry with no force
188 // is accepted as a minimum and the wall energy is recorded as that
189 // minimum's energy. It then reaches the barrier as E_saddle - 1e6, which
190 // eOn reports as a negative barrier and discards -- a real 0.062 eV copper
191 // saddle was lost exactly this way. Return a force far above any
192 // convergence threshold so the point can never be mistaken for a
193 // stationary one, alternating its sign so the frame gains no net force.
194 for (long i = 0; i < 3 * N; ++i) {
195 F[i] = 0.0;
196 }
197 for (long i = 0; i < N; ++i) {
198 F[3 * i] = (i % 2 == 0) ? 1.0 : -1.0;
199 }
200}
201
202// Blocking read/write of exactly n bytes over a pipe. Returns false on EOF or
203// error, so a dead peer is detected rather than silently producing garbage.
204bool readExact(int fd, void *buf, size_t n) {
205 auto *p = static_cast<char *>(buf);
206 while (n > 0) {
207 ssize_t r = read(fd, p, n);
208 if (r <= 0) {
209 if (r < 0 && errno == EINTR)
210 continue;
211 return false;
212 }
213 p += r;
214 n -= static_cast<size_t>(r);
215 }
216 return true;
217}
218bool writeExact(int fd, const void *buf, size_t n) {
219 const auto *p = static_cast<const char *>(buf);
220 while (n > 0) {
221 ssize_t w = write(fd, p, n);
222 if (w < 0) {
223 if (errno == EINTR)
224 continue;
225 return false;
226 }
227 p += w;
228 n -= static_cast<size_t>(w);
229 }
230 return true;
231}
232} // namespace
233
235 if (workerSpawned)
236 return;
237
238 int reqPipe[2]; // parent -> child
239 int resPipe[2]; // child -> parent
240 if (pipe(reqPipe) != 0) {
241 throw std::runtime_error("LAMMPSPot: failed to create worker pipes");
242 }
243 if (pipe(resPipe) != 0) {
244 close(reqPipe[0]);
245 close(reqPipe[1]);
246 throw std::runtime_error("LAMMPSPot: failed to create worker pipes");
247 }
248
249 // Fork BEFORE opening any LAMMPS instance in this process, so MPI is first
250 // initialised inside the child. Each child is its own process with its own
251 // MPI_COMM_WORLD; concurrent children never share a communicator.
252 pid_t pid = fork();
253 if (pid < 0) {
254 close(reqPipe[0]);
255 close(reqPipe[1]);
256 close(resPipe[0]);
257 close(resPipe[1]);
258 throw std::runtime_error("LAMMPSPot: fork for worker failed");
259 }
260
261 if (pid == 0) {
262 // Child: keep reqPipe read end and resPipe write end.
263 close(reqPipe[1]);
264 close(resPipe[0]);
265 reqFd = reqPipe[0];
266 resFd = resPipe[1];
267 // Client main arms feenableexcept unconditionally. The worker inherits
268 // that mask; LAMMPS EAM (PairEAM::compute) performs IEEE divisions that
269 // can raise FE_DIVBYZERO on near-coincident pairs during saddle / product
270 // minimisations. Under trapping that SIGFPEs, and the continue handler
271 // without MXCSR masking re-storms forever in the child (GB of identical
272 // "FPE (continuing)" lines). External pot code expects soft IEEE defaults;
273 // demote trapping for the whole worker process before any LAMMPS call.
275 runWorkerLoop(); // never returns
276 }
277
278 // Parent: keep reqPipe write end and resPipe read end.
279 //
280 // Writing to a worker that has already exited raises SIGPIPE, whose default
281 // action kills the client outright -- before writeExact can return the
282 // error the caller is written to handle. A search that had converged on a
283 // saddle at 0.062 eV died this way with signal 13 as its endpoints were
284 // about to be minimised. Ignoring it turns the same condition into an
285 // EPIPE return, which reaches the geometry-rejection path and respawns.
286 std::signal(SIGPIPE, SIG_IGN);
287 close(reqPipe[0]);
288 close(resPipe[1]);
289 reqFd = reqPipe[1];
290 resFd = resPipe[0];
291 workerPid = pid;
292 workerSpawned = true;
293}
294
296 workerChild_ = true;
297 // Running in the forked child. Evaluate forces with an in-process LAMMPS
298 // (this child's own MPI_COMM_WORLD) and stream results back to the parent.
299 for (;;) {
300 long N = 0;
301 if (!readExact(reqFd, &N, sizeof(N))) {
302 _exit(0); // request pipe closed -> shut down cleanly
303 }
304 if (N < 0) {
305 _exit(0); // explicit shutdown sentinel from stopWorker()
306 }
307 std::vector<int> atomicNrs(static_cast<size_t>(N));
308 std::vector<double> R(static_cast<size_t>(3 * N));
309 double box[9];
310 if (!readExact(reqFd, atomicNrs.data(),
311 sizeof(int) * static_cast<size_t>(N)) ||
312 !readExact(reqFd, box, sizeof(box)) ||
313 !readExact(reqFd, R.data(),
314 sizeof(double) * static_cast<size_t>(3 * N))) {
315 _exit(1);
316 }
317 std::vector<double> mask(static_cast<size_t>(3 * N), 0.0);
318 if (!readExact(reqFd, mask.data(),
319 sizeof(double) * static_cast<size_t>(3 * N))) {
320 _exit(1);
321 }
322 setFixedMask(N, mask.data());
323
324 std::vector<double> F(static_cast<size_t>(3 * N), 0.0);
325 double U = 0.0;
326 int status = 0;
327 try {
328 forceLocal(N, R.data(), atomicNrs.data(), F.data(), &U, box);
329 } catch (...) {
330 status = 1;
331 haveStress_ = false;
332 }
333 double stressRaw[9] = {};
334 if (haveStress_) {
335 for (int i = 0; i < 9; ++i) {
336 stressRaw[i] = stress_(i / 3, i % 3);
337 }
338 }
339
340 if (!writeExact(resFd, &status, sizeof(status)) ||
341 !writeExact(resFd, &U, sizeof(U)) ||
342 !writeExact(resFd, F.data(),
343 sizeof(double) * static_cast<size_t>(3 * N)) ||
344 !writeExact(resFd, stressRaw, sizeof(stressRaw))) {
345 _exit(1);
346 }
347 }
348}
349
351 if (!workerSpawned)
352 return;
353 if (reqFd >= 0) {
354 // Send an explicit shutdown sentinel, then close. A sentinel (rather than
355 // relying on pipe EOF) guarantees the child exits even when sibling worker
356 // processes hold an inherited copy of this write end.
357 long sentinel = -1;
358 writeExact(reqFd, &sentinel, sizeof(sentinel));
359 close(reqFd);
360 reqFd = -1;
361 }
362 if (resFd >= 0) {
363 close(resFd);
364 resFd = -1;
365 }
366 if (workerPid > 0) {
367 // A wedged worker never acts on the sentinel or the closed pipe, and an
368 // unconditional wait then blocks the client forever at no CPU cost -- the
369 // hang this teardown exists to avoid. Give the child a brief chance to
370 // exit on its own, then insist.
371 int st = 0;
372 bool reaped = false;
373 for (int i = 0; i < 100; ++i) { // up to ~1 s
374 pid_t r = waitpid(workerPid, &st, WNOHANG);
375 if (eonc::lammpsWorkerReaped(r, workerPid, errno)) {
376 reaped = true;
377 break;
378 }
379 usleep(10000);
380 }
381 if (!reaped) {
382 kill(workerPid, SIGKILL);
383 for (;;) {
384 pid_t r = waitpid(workerPid, &st, 0);
385 if (r == workerPid || (r < 0 && errno != EINTR)) {
386 break;
387 }
388 }
389 }
390 workerPid = -1;
391 }
392 workerSpawned = false;
393}
394
395bool LAMMPSPot::noteScreenGeometry(long N, const double *box) {
396 bool changed = !screenHaveGeom_ || N != screenAtoms_;
397 if (!changed) {
398 for (int i = 0; i < 9; ++i) {
399 if (screenBox_[i] != box[i]) {
400 changed = true;
401 break;
402 }
403 }
404 }
405 screenHaveGeom_ = true;
406 screenAtoms_ = N;
407 std::memcpy(screenBox_, box, sizeof(screenBox_));
408 return changed;
409}
410#endif // !EONMPI && !IS_WINDOWS
411
412void LAMMPSPot::force(long N, const double *R, const int *atomicNrs, double *F,
413 double *U, double *variance, const double *box) {
414 variance = nullptr;
415
416#ifdef EONMPI
417 std::lock_guard<std::mutex> stateLock(workerMutex);
418 forceLocal(N, R, atomicNrs, F, U, box);
419#elif defined(IS_WINDOWS)
420 // No fork/pipe on Windows; call forceLocal directly.
421 std::lock_guard<std::mutex> stateLock(workerMutex);
422 forceLocal(N, R, atomicNrs, F, U, box);
423#else
424 // Drive the dedicated worker process so this image's LAMMPS runs in its own
425 // process (own MPI_COMM_WORLD). Per-image NEB threads thus evaluate forces
426 // as truly concurrent processes with no shared-communicator contention.
427 std::lock_guard<std::mutex> workerLock(workerMutex);
428 if (workerRespawnsLeft <= 0) {
429 rejectGeometry(U, F, N);
430 return;
431 }
432 const bool newWorker = !workerSpawned;
433 ensureWorker();
434 const bool screenRestart = newWorker || noteScreenGeometry(N, box);
435
436 std::vector<double> mask(static_cast<size_t>(3 * N), 0.0);
437 {
438 std::lock_guard<std::mutex> lock(maskMutex_);
439 if (maskN_ == N && fixedMask_.size() == static_cast<size_t>(3 * N)) {
440 mask = fixedMask_;
441 }
442 }
443 if (!writeExact(reqFd, &N, sizeof(N)) ||
444 !writeExact(reqFd, atomicNrs, sizeof(int) * static_cast<size_t>(N)) ||
445 !writeExact(reqFd, box, sizeof(double) * 9) ||
446 !writeExact(reqFd, R, sizeof(double) * static_cast<size_t>(3 * N)) ||
447 !writeExact(reqFd, mask.data(),
448 sizeof(double) * static_cast<size_t>(3 * N))) {
449 // A worker stopped by an earlier rejected geometry leaves the request
450 // pipe closed, so the first send after it fails. Respawning happens on
451 // the next evaluation; reject this one rather than end the client.
453 EONC_LOG_WARNING("[LAMMPSPot] send to worker failed; {} respawns left "
454 "(eon-7416)",
456 stopWorker();
457 rejectGeometry(U, F, N);
458 return;
459 }
460
461 // eon-7416: a worker stuck on a pathological geometry (LAMMPS spinning, or
462 // a NaN it never returns) would make the blocking read below hang until the
463 // akmc pass times out. Bound the wait: if the worker is silent past a
464 // generous per-eval deadline, kill and reap it (a stuck worker never reaches
465 // EOF, so a plain waitpid would block too) and fail this evaluation so the
466 // search discards the geometry; ensureWorker respawns on the next call.
467 {
468 struct pollfd pfd;
469 pfd.fd = resFd;
470 pfd.events = POLLIN;
471 pfd.revents = 0;
472 int pr = poll(&pfd, 1, 90000); // 90 s: orders beyond a normal force eval
473 if (pr <= 0) {
474 if (workerPid > 0) {
475 kill(workerPid, SIGKILL);
476 }
479 "[LAMMPSPot] worker force eval timed out; {} respawns left "
480 "(eon-7416)",
482 stopWorker();
483 rejectGeometry(U, F, N);
484 return;
485 }
486 }
487 int status = 0;
488 double stressRaw[9] = {};
489 if (!readExact(resFd, &status, sizeof(status)) ||
490 !readExact(resFd, U, sizeof(double)) ||
491 !readExact(resFd, F, sizeof(double) * static_cast<size_t>(3 * N)) ||
492 !readExact(resFd, stressRaw, sizeof(stressRaw))) {
495 "[LAMMPSPot] worker died during force eval; {} respawns left "
496 "(eon-7416)",
498 stopWorker();
499 rejectGeometry(U, F, N);
500 return;
501 }
502 if (screenRestart) {
504 }
505 stress_.setZero();
506 for (int row = 0; row < 3; ++row) {
507 for (int col = 0; col < 3; ++col) {
508 stress_(row, col) = stressRaw[row * 3 + col];
509 }
510 }
511 haveStress_ = status == 0 && stress_.allFinite();
512 if (status != 0) {
516 "[LAMMPSPot] worker reported an evaluation error; {} respawns left "
517 "(eon-7416)",
519 stopWorker();
520 rejectGeometry(U, F, N);
521 return;
522 }
524 // A saddle search that never terminates silently truncates the event
525 // table: the KMC residence time is 1/sum_j k_j over the discovered
526 // mechanisms, so a dropped search removes a term and biases the clock
527 // (Pedersen and Jónsson, Math. Comput. Simul. 80, 1487 (2010),
528 // doi:10.1016/j.matcom.2009.02.010, Fig. 1; Alexander and Schuh,
529 // Modelling Simul. Mater. Sci. Eng. 24, 065014 (2016),
530 // doi:10.1088/0965-0393/24/6/065014, on catalog completeness).
531 // An over-aggressive saddle-search kick can drive atoms on top of
532 // each other; the EAM force overflows to NaN/Inf and LAMMPS returns it
533 // rather than crashing. The min-mode search then spins on non-finite
534 // gradients until the akmc pass times out (0 processes). Reject the
535 // evaluation so the search discards that displacement and continues.
536 // Reject the geometry, not the process. Nothing between this call and
537 // main catches a throw here, so the client terminates: a search that was
538 // making progress is lost, and every other search sharing the pass goes
539 // with it. A large finite energy with zeroed forces reads to the
540 // optimiser as an impassable wall, so it backs out of the step and the
541 // search abandons this configuration and carries on.
542 bool nonfinite = !std::isfinite(*U);
543 for (long i = 0; i < 3 * N && !nonfinite; ++i) {
544 nonfinite = !std::isfinite(F[i]);
545 }
546 if (nonfinite) {
547 EONC_LOG_WARNING("[LAMMPSPot] non-finite force or energy; rejecting "
548 "geometry (eon-7416)");
549 rejectGeometry(U, F, N);
550 }
551#endif
552}
553
554void LAMMPSPot::forceLocal(long N, const double *R, const int *atomicNrs,
555 double *F, double *U, const double *box) {
556 // Same contract as ASE / Metatomic: external pot libraries are not written
557 // for FE traps. Cover in-process (EONMPI / Windows) and any path that still
558 // has trapping armed when forceLocal runs. Always restore so FE traps do not
559 // stay demoted for the rest of the process after the first force call.
560 eonc::FPEHandler fpeh;
561 fpeh.eat_fpe();
562 try {
563 auto &lmp = loader_;
564
565 bool newLammps = false;
566 for (int i = 0; i < 9; i++) {
567 if (oldBox[i] != box[i])
568 newLammps = true;
569 }
570 if (numberOfAtoms != N)
571 newLammps = true;
572 if (newLammps) {
573 makeNewLAMMPS(N, R, atomicNrs, box);
574 }
575 if (!LAMMPSObj) {
576 throw std::runtime_error("Should have a LAMMPS instance by now");
577 }
578
579 lmp.scatter_atoms(LAMMPSObj, "x", 1, 3, const_cast<double *>(R));
580 applySetforce(N);
581 // New instance / box change: rebuild neighbors. create_atoms sits at
582 // the origin; pre no would evaluate on that neighbor list.
583 if (newLammps) {
584 lammpsCommand("run 1 pre yes post no");
585 } else {
586 lammpsCommand("run 1 pre no post no");
587 }
588
589 auto *pe =
590 static_cast<double *>(lmp.extract_variable(LAMMPSObj, "pe", nullptr));
591 auto *fx =
592 static_cast<double *>(lmp.extract_variable(LAMMPSObj, "fx", "all"));
593 auto *fy =
594 static_cast<double *>(lmp.extract_variable(LAMMPSObj, "fy", "all"));
595 auto *fz =
596 static_cast<double *>(lmp.extract_variable(LAMMPSObj, "fz", "all"));
597 if (!pe || !fx || !fy || !fz) {
598 if (pe)
599 free(pe);
600 if (fx)
601 free(fx);
602 if (fy)
603 free(fy);
604 if (fz)
605 free(fz);
606 throw std::runtime_error(
607 "LAMMPS: extract_variable returned null (pe/fx/fy/fz)");
608 }
609 *U = *pe;
610 free(pe);
611
612 for (long i = 0; i < N; i++) {
613 F[3 * i + 0] = fx[i];
614 F[3 * i + 1] = fy[i];
615 F[3 * i + 2] = fz[i];
616 }
617
618 // Convert kCal/mol -> eV if LAMMPS is using real units
619 if (realunits) {
620 constexpr double kcalPerEv = 23.0609;
621 *U /= kcalPerEv;
622 for (long i = 0; i < 3 * N; i++) {
623 F[i] /= kcalPerEv;
624 }
625 }
626
627 free(fx);
628 free(fy);
629 free(fz);
630
631 // LAMMPS pressure is positive when the cell pushes outward. sigma =
632 // (1/V) dE/dε has the opposite sign. metal reports bars, real reports
633 // atmospheres. 1 eV/Angstrom^3 = 1.6021766208e6 bar.
634 auto *sxx = static_cast<double *>(
635 lmp.extract_variable(LAMMPSObj, "eon_sxx", nullptr));
636 auto *syy = static_cast<double *>(
637 lmp.extract_variable(LAMMPSObj, "eon_syy", nullptr));
638 auto *szz = static_cast<double *>(
639 lmp.extract_variable(LAMMPSObj, "eon_szz", nullptr));
640 auto *sxy = static_cast<double *>(
641 lmp.extract_variable(LAMMPSObj, "eon_sxy", nullptr));
642 auto *sxz = static_cast<double *>(
643 lmp.extract_variable(LAMMPSObj, "eon_sxz", nullptr));
644 auto *syz = static_cast<double *>(
645 lmp.extract_variable(LAMMPSObj, "eon_syz", nullptr));
646 const bool got = sxx && syy && szz && sxy && sxz && syz;
647 auto release = [&]() {
648 if (sxx)
649 free(sxx);
650 if (syy)
651 free(syy);
652 if (szz)
653 free(szz);
654 if (sxy)
655 free(sxy);
656 if (sxz)
657 free(sxz);
658 if (syz)
659 free(syz);
660 };
661 if (!got) {
662 release();
663 haveStress_ = false;
664 stress_.setZero();
665 } else {
666 constexpr double barPerEvA3 = 1.6021766208e6;
667 constexpr double barPerAtm = 1.01325;
668 const double toEv =
669 realunits ? -(barPerAtm / barPerEvA3) : -(1.0 / barPerEvA3);
670 stress_.setZero();
671 stress_(0, 0) = (*sxx) * toEv;
672 stress_(1, 1) = (*syy) * toEv;
673 stress_(2, 2) = (*szz) * toEv;
674 stress_(0, 1) = stress_(1, 0) = (*sxy) * toEv;
675 stress_(0, 2) = stress_(2, 0) = (*sxz) * toEv;
676 stress_(1, 2) = stress_(2, 1) = (*syz) * toEv;
677 haveStress_ = stress_.allFinite();
678 release();
679 }
680 } catch (...) {
681 fpeh.restore_fpe();
682 throw;
683 }
684 fpeh.restore_fpe();
685}
686
688 // makeNewLAMMPS runs in the worker. The child writes the screen file and
689 // must not call the process logger; the parent copies it after the reply.
690 if (workerChild_ || !lammpsLogging_ || lammpsScreenPath_.empty()) {
691 return;
692 }
693 std::error_code ec;
694 const auto sz = std::filesystem::file_size(lammpsScreenPath_, ec);
695 if (ec) {
696 return;
697 }
698 const auto fileSize = static_cast<std::int64_t>(sz);
701 lammpsScreenRestart_ = false;
702 std::ifstream in(lammpsScreenPath_, std::ios::in | std::ios::binary);
703 if (!in) {
704 return;
705 }
706 in.seekg(static_cast<std::streamoff>(lammpsScreenPos_));
707 std::string line;
708 while (std::getline(in, line)) {
709 if (!line.empty() && line.back() == '\r') {
710 line.pop_back();
711 }
712 if (!line.empty()) {
713 EONC_LOG_INFO("{}", line);
714 }
715 }
716 in.clear();
717 in.seekg(0, std::ios::end);
718 const auto end = in.tellg();
719 if (end >= 0) {
720 lammpsScreenPos_ = static_cast<std::int64_t>(end);
721 }
722}
723
724void LAMMPSPot::lammpsCommand(const char *cmd) {
725 loader_.command(LAMMPSObj, cmd);
726 // The forked worker must not call the process logger. The parent copies
727 // the screen file after the child returns the force.
728 if (!workerChild_) {
730 }
731}
732
733void LAMMPSPot::makeNewLAMMPS(long N, const double *R, const int *atomicNrs,
734 const double *box) {
735 auto &lmp = loader_;
736
737 numberOfAtoms = N;
738 std::memcpy(oldBox, box, 9 * sizeof(double));
739
740 if (LAMMPSObj != nullptr) {
742 loader_.close(LAMMPSObj);
743 LAMMPSObj = nullptr;
744 }
745
746 // Map atomic numbers to LAMMPS type indices (1-based)
747 std::map<int, int> type_map;
748 int ntypes = 0;
749 for (long i = 0; i < N; i++) {
750 if (type_map.count(atomicNrs[i]) == 0) {
751 type_map.insert({atomicNrs[i], ++ntypes});
752 }
753 }
754
755#ifdef EONMPI
756 const std::vector<std::string> argStore =
758 std::vector<char *> argPtrs;
759 argPtrs.reserve(argStore.size());
760 for (const std::string &arg : argStore) {
761 argPtrs.push_back(const_cast<char *>(arg.c_str()));
762 }
763 int lmpargc = static_cast<int>(argPtrs.size());
764 char **lmpargv = argPtrs.data();
765 if (!lmp.open_mpi) {
766 throw std::runtime_error(
767 "LAMMPS library found but lacks MPI support (lammps_open not found).\n"
768 "Install an MPI-enabled LAMMPS build.");
769 }
770 MPI_Comm inst_comm = MPI_COMM_NULL;
771 MPI_Comm_dup(mpiComm, &inst_comm); // private comm per per-image instance
772 LAMMPSObj = lmp.open_mpi(lmpargc, lmpargv, inst_comm, nullptr);
773#else
774 const std::vector<std::string> argStore =
776 std::vector<char *> argPtrs;
777 argPtrs.reserve(argStore.size());
778 for (const std::string &arg : argStore) {
779 argPtrs.push_back(const_cast<char *>(arg.c_str()));
780 }
781 int lmpargc = static_cast<int>(argPtrs.size());
782 LAMMPSObj = lmp.open_no_mpi(lmpargc, argPtrs.data(), nullptr);
783#endif
784 // -screen opens with truncation. The next copy starts at the new file.
786
787 if (lammpsThr > 0) {
788 std::string cmd = std::format("package omp {} force/neigh", lammpsThr);
789 lammpsCommand(cmd.c_str());
790 }
791
792 // Detect units from in.lammps: look for "#!units real" marker
793 realunits = false;
794 if (std::filesystem::exists("in.lammps")) {
795 std::ifstream infile("in.lammps");
796 std::string line;
797 while (std::getline(infile, line)) {
798 if (line == "#!units real") {
799 realunits = true;
800 break;
801 }
802 }
803 } else {
804 if (LAMMPSObj != nullptr) {
806 lmp.close(LAMMPSObj);
807 LAMMPSObj = nullptr;
808 }
809 throw std::runtime_error(
810 "LAMMPS: in.lammps not found in working directory");
811 }
812
813 if (realunits) {
814 lammpsCommand("units real");
815 } else {
816 lammpsCommand("units metal");
817 }
818
819 lammpsCommand("atom_style charge");
820 lammpsCommand("atom_modify map array sort 0 0");
821 lammpsCommand("neigh_modify delay 1");
822
823 // LAMMPS restricted triclinic: (ax, by, cz, bx, cx, cy).
824 // Row-major Matter cell also has ay, az, bz at box[1], box[2], box[5].
825 constexpr double kTiltCut = 1.0e-8;
826 if (std::abs(box[1]) > kTiltCut || std::abs(box[2]) > kTiltCut ||
827 std::abs(box[5]) > kTiltCut) {
828 throw std::runtime_error(
829 "LAMMPS: cell is not restricted triclinic (ay/az/bz must be ~0)");
830 }
831 std::string region_cmd =
832 std::format("region cell prism 0 {} 0 {} 0 {} {} {} {} units box", box[0],
833 box[4], box[8], box[3], box[6], box[7]);
834 lammpsCommand(region_cmd.c_str());
835
836 std::string create_box_cmd = std::format("create_box {} cell", ntypes);
837 lammpsCommand(create_box_cmd.c_str());
838
839 // Initialize atoms
840 for (long i = 0; i < N; i++) {
841 std::string atom_cmd =
842 std::format("create_atoms {} single {} {} {} units box",
843 type_map[atomicNrs[i]], 0.0, 0.0, 0.0);
844 lammpsCommand(atom_cmd.c_str());
845 }
846
847 lammpsCommand("mass * 1.0");
848
849 // Load user LAMMPS input script
850 lmp.file(LAMMPSObj, "in.lammps");
851
852 // Define variables for force/energy extraction
853 lammpsCommand("variable fx atom fx");
854 lammpsCommand("variable fy atom fy");
855 lammpsCommand("variable fz atom fz");
856 lammpsCommand("variable pe equal pe");
857 // Kinetic part omitted. The six components are pxx pyy pzz pxy pxz pyz.
858 lammpsCommand("compute eon_press all pressure NULL virial");
859 lammpsCommand("variable eon_sxx equal c_eon_press[1]");
860 lammpsCommand("variable eon_syy equal c_eon_press[2]");
861 lammpsCommand("variable eon_szz equal c_eon_press[3]");
862 lammpsCommand("variable eon_sxy equal c_eon_press[4]");
863 lammpsCommand("variable eon_sxz equal c_eon_press[5]");
864 lammpsCommand("variable eon_syz equal c_eon_press[6]");
865}
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:255
#define EONC_LOG_INFO(...)
Definition EonLogger.h:249
double oldBox[9]
Definition LAMMPSPot.h:113
int workerPid
Definition LAMMPSPot.h:153
bool screenHaveGeom_
Definition LAMMPSPot.h:159
bool lammpsScreenRestart_
Definition LAMMPSPot.h:107
std::string lammpsScreenPath_
Definition LAMMPSPot.h:103
void setFixedMask(long nAtoms, const double *isFixed) override
Optional frozen-atom mask (nAtoms*3, 1.0 = fixed).
Definition LAMMPSPot.cpp:87
std::mutex maskMutex_
Definition LAMMPSPot.h:99
std::vector< double > fixedMask_
Definition LAMMPSPot.h:121
void makeNewLAMMPS(long N, const double *R, const int *atomicNrs, const double *box)
void drainLammpsScreen()
int lammpsThr
Definition LAMMPSPot.h:96
LAMMPSPot(const eonc::Parameters &p)
Production: process-default LammpsLoader and POSIX worker isolation.
Definition LAMMPSPot.cpp:44
Matrix3d stress_
Definition LAMMPSPot.h:119
bool lammpsLogging_
Definition LAMMPSPot.h:97
bool workerSpawned
Definition LAMMPSPot.h:156
void cleanMemory()
void lammpsCommand(const char *cmd)
int lammpsLogIndex_
Definition LAMMPSPot.h:98
int workerRespawnsLeft
Definition LAMMPSPot.h:144
long maskN_
Definition LAMMPSPot.h:122
bool realunits
Definition LAMMPSPot.h:118
void stopWorker()
bool haveStress_
Definition LAMMPSPot.h:120
long screenAtoms_
Definition LAMMPSPot.h:160
void * LAMMPSObj
Definition LAMMPSPot.h:114
std::int64_t lammpsScreenPos_
Definition LAMMPSPot.h:104
void ensureWorker()
void runWorkerLoop()
long numberOfAtoms
Definition LAMMPSPot.h:112
double screenBox_[9]
Definition LAMMPSPot.h:161
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
bool noteScreenGeometry(long N, const double *box)
void forceLocal(long N, const double *R, const int *atomicNrs, double *F, double *U, const double *box)
bool workerChild_
Definition LAMMPSPot.h:108
std::mutex workerMutex
Definition LAMMPSPot.h:126
void applySetforce(long N)
eonc::ILammpsLoader & loader_
Definition LAMMPSPot.h:95
Potential(PotType a_ptype)
Production default: construction-scope registry, else PotRegistry::get().
RAII resource manager for the ARTn C library with global synchronization.
std::int64_t lammpsScreenCursor(std::int64_t pos, std::int64_t fileSize, bool restarted)
Byte offset at which to keep copying a LAMMPS screen file.
Definition LAMMPSPot.h:41
bool lammpsWorkerReaped(long got, long child, int err)
True when waitpid has collected the child. EINTR is not a collection.
Definition LAMMPSPot.h:31
std::vector< std::string > lammpsOpenArgs(bool logging, bool with_omp, const std::string &screen)
LAMMPS argv.
Definition LAMMPSPot.h:51
void disableFPE()