Loading...
Searching...
No Matches
LAMMPSPot Class Reference

#include <LAMMPSPot.h>

Inheritance diagram for LAMMPSPot:

Public Member Functions

bool needsPerImageInstance () const noexcept override
 Whether NEB should create separate Potential instances per image for true parallel force evaluation.
 LAMMPSPot (const eonc::Parameters &p)
 Production: process-default LammpsLoader and POSIX worker isolation.
 LAMMPSPot (const eonc::Parameters &p, eonc::ILammpsLoader &loader)
 Test seam: injected loader, no worker fork, no process-default load.
 ~LAMMPSPot ()
void cleanMemory ()
void force (long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
void setFixedMask (long nAtoms, const double *isFixed) override
 Optional frozen-atom mask (nAtoms*3, 1.0 = fixed).
bool computesStress () const noexcept override
 True when force() leaves a Cauchy stress that cauchyStress() can read until the next force() on this instance.
Matrix3d cauchyStress () const override
 Cauchy stress in eV/Angstrom^3.
Public Member Functions inherited from eonc::Potential
 Potential (PotType a_ptype)
 Production default: construction-scope registry, else PotRegistry::get().
 Potential (PotType a_ptype, IPotRegistry &registry)
 Test seam: injected registry, no process-default get() counters.
 Potential (PotType a_ptype, const Parameters &p)
 Potential (const Parameters &a_params)
virtual ~Potential ()
void force (std::span< const double > positions, std::span< const int > atomicNrs, std::span< double > forces, double *energy, double *variance, std::span< const double > box)
 C++ call site: size-checked view over the raw FFI force().
std::tuple< double, AtomMatrix > get_ef (const AtomMatrix &pos, const VectorXi &atmnrs, const Matrix3d &box)
PotType getType () const
virtual double finiteCutoff () const noexcept
 Finite interaction range in position length units.
virtual bool isSurrogate () const noexcept
 Whether this is a surrogate (GP) potential.
virtual bool requiresIsolatedMoleculeLayout () const noexcept
 True for molecular QM / non-PBC backends (NWChem socket, ASE ORCA/NWChem, …).
virtual bool isThreadSafe () const noexcept
 Whether this potential's force() can be called from multiple threads on the SAME instance.
virtual unsigned layoutFlags () const noexcept
virtual bool isSharedInstanceThreadSafe () const noexcept
 Conservative gate for sharing one Potential instance across threads.
virtual std::shared_ptr< Potential > clonePotential () const
 Independent instance that does not reload from disk.
virtual bool supportsBatchEvaluation () const noexcept
 Whether this potential supports batched evaluation of N systems in a single call.
virtual 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)
 Evaluate forces for N systems in a single call.
virtual 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)

Private Member Functions

 LAMMPSPot (const eonc::Parameters &p, eonc::ILammpsLoader &loader, bool isolate_worker)
void makeNewLAMMPS (long N, const double *R, const int *atomicNrs, const double *box)
void applySetforce (long N)
void ensureWorker ()
bool noteScreenGeometry (long N, const double *box)
void runWorkerLoop ()
void stopWorker ()
void forceLocal (long N, const double *R, const int *atomicNrs, double *F, double *U, const double *box)
void lammpsCommand (const char *cmd)
void drainLammpsScreen ()

Private Attributes

eonc::ILammpsLoader & loader_
int lammpsThr {0}
bool lammpsLogging_ {false}
int lammpsLogIndex_ {0}
std::mutex maskMutex_
std::string lammpsScreenPath_
std::int64_t lammpsScreenPos_ {0}
bool lammpsScreenRestart_ {false}
bool workerChild_ {false}
long numberOfAtoms {0}
double oldBox [9] {}
void * LAMMPSObj {nullptr}
bool realunits {false}
Matrix3d stress_ {Matrix3d::Zero()}
bool haveStress_ {false}
std::vector< double > fixedMask_
long maskN_ {0}
std::mutex workerMutex
int workerRespawnsLeft {3}
int workerPid {-1}
int reqFd {-1}
int resFd {-1}
bool workerSpawned {false}
bool screenHaveGeom_ {false}
long screenAtoms_ {0}
double screenBox_ [9] {}

Additional Inherited Members

Public Types inherited from eonc::Potential
enum class  PotLayout : unsigned { InProcess = 1u << 0 , NeedsWorkingDirectory = 1u << 1 , Subprocess = 1u << 2 }
 How the pot is executed. Combine with bitwise or. More...
Public Attributes inherited from eonc::Potential
std::atomic< size_t > forceCallCounter
Protected Attributes inherited from eonc::Potential
PotType ptype

Detailed Description

Definition at line 70 of file LAMMPSPot.h.

Constructor & Destructor Documentation

◆ LAMMPSPot() [1/3]

LAMMPSPot::LAMMPSPot ( const eonc::Parameters & p)
explicit

Production: process-default LammpsLoader and POSIX worker isolation.

Definition at line 44 of file LAMMPSPot.cpp.

LAMMPSPot(const eonc::Parameters &p)
Production: process-default LammpsLoader and POSIX worker isolation.
Definition LAMMPSPot.cpp:44
static LammpsLoader & instance()
Thread-safe singleton accessor (Meyer's pattern). Does not dlopen.

◆ LAMMPSPot() [2/3]

LAMMPSPot::LAMMPSPot ( const eonc::Parameters & p,
eonc::ILammpsLoader & loader )

Test seam: injected loader, no worker fork, no process-default load.

Definition at line 47 of file LAMMPSPot.cpp.

48 : LAMMPSPot(p, loader, false) {}

◆ ~LAMMPSPot()

LAMMPSPot::~LAMMPSPot ( )

Definition at line 85 of file LAMMPSPot.cpp.

85{ cleanMemory(); }
void cleanMemory()

◆ LAMMPSPot() [3/3]

LAMMPSPot::LAMMPSPot ( const eonc::Parameters & p,
eonc::ILammpsLoader & loader,
bool isolate_worker )
private

Definition at line 50 of file LAMMPSPot.cpp.

52 : eonc::Potential(p),
53 loader_{loader},
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}
std::string lammpsScreenPath_
Definition LAMMPSPot.h:103
int lammpsThr
Definition LAMMPSPot.h:96
bool lammpsLogging_
Definition LAMMPSPot.h:97
int lammpsLogIndex_
Definition LAMMPSPot.h:98
void ensureWorker()
eonc::ILammpsLoader & loader_
Definition LAMMPSPot.h:95
const potential_options_t & potential_options() const

Member Function Documentation

◆ applySetforce()

void LAMMPSPot::applySetforce ( long N)
private

Definition at line 101 of file LAMMPSPot.cpp.

101 {
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}
std::mutex maskMutex_
Definition LAMMPSPot.h:99
std::vector< double > fixedMask_
Definition LAMMPSPot.h:121
void lammpsCommand(const char *cmd)
long maskN_
Definition LAMMPSPot.h:122
void * LAMMPSObj
Definition LAMMPSPot.h:114
bool workerChild_
Definition LAMMPSPot.h:108

◆ cauchyStress()

Matrix3d LAMMPSPot::cauchyStress ( ) const
inlinenodiscardoverridevirtual

Cauchy stress in eV/Angstrom^3.

sigma = (1/V) dE/dε for the right strain h <- h (I+ε) at fixed fractional coordinates.

Reimplemented from eonc::Potential.

Definition at line 90 of file LAMMPSPot.h.

90{ return stress_; }
Matrix3d stress_
Definition LAMMPSPot.h:119

◆ cleanMemory()

void LAMMPSPot::cleanMemory ( )

Definition at line 156 of file LAMMPSPot.cpp.

156 {
157#if !defined(EONMPI) && !defined(IS_WINDOWS)
158 stopWorker();
159#endif
160 if (LAMMPSObj != nullptr) {
162 loader_.close(LAMMPSObj);
163 LAMMPSObj = nullptr;
164 }
165}
void drainLammpsScreen()
void stopWorker()

◆ computesStress()

bool LAMMPSPot::computesStress ( ) const
inlinenodiscardoverridevirtualnoexcept

True when force() leaves a Cauchy stress that cauchyStress() can read until the next force() on this instance.

Reimplemented from eonc::Potential.

Definition at line 86 of file LAMMPSPot.h.

86 {
87 return haveStress_;
88 }
bool haveStress_
Definition LAMMPSPot.h:120

◆ drainLammpsScreen()

void LAMMPSPot::drainLammpsScreen ( )
private

Definition at line 687 of file LAMMPSPot.cpp.

687 {
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}
#define EONC_LOG_INFO(...)
Definition EonLogger.h:249
bool lammpsScreenRestart_
Definition LAMMPSPot.h:107
std::int64_t lammpsScreenPos_
Definition LAMMPSPot.h:104
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

◆ ensureWorker()

void LAMMPSPot::ensureWorker ( )
private

Definition at line 234 of file LAMMPSPot.cpp.

234 {
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}
int workerPid
Definition LAMMPSPot.h:153
bool workerSpawned
Definition LAMMPSPot.h:156
void runWorkerLoop()
void close(Handle h) noexcept
Definition DynLib.h:77
void disableFPE()

◆ force()

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

Implements eonc::Potential.

Definition at line 412 of file LAMMPSPot.cpp.

413 {
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}
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:255
int workerRespawnsLeft
Definition LAMMPSPot.h:144
bool noteScreenGeometry(long N, const double *box)
void forceLocal(long N, const double *R, const int *atomicNrs, double *F, double *U, const double *box)
std::mutex workerMutex
Definition LAMMPSPot.h:126

◆ forceLocal()

void LAMMPSPot::forceLocal ( long N,
const double * R,
const int * atomicNrs,
double * F,
double * U,
const double * box )
private

Definition at line 554 of file LAMMPSPot.cpp.

555 {
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}
double oldBox[9]
Definition LAMMPSPot.h:113
void makeNewLAMMPS(long N, const double *R, const int *atomicNrs, const double *box)
bool realunits
Definition LAMMPSPot.h:118
long numberOfAtoms
Definition LAMMPSPot.h:112
void applySetforce(long N)
void release(State *state)

◆ lammpsCommand()

void LAMMPSPot::lammpsCommand ( const char * cmd)
private

Definition at line 724 of file LAMMPSPot.cpp.

724 {
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}

◆ makeNewLAMMPS()

void LAMMPSPot::makeNewLAMMPS ( long N,
const double * R,
const int * atomicNrs,
const double * box )
private

Definition at line 733 of file LAMMPSPot.cpp.

734 {
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}
std::vector< std::string > lammpsOpenArgs(bool logging, bool with_omp, const std::string &screen)
LAMMPS argv.
Definition LAMMPSPot.h:51

◆ needsPerImageInstance()

bool LAMMPSPot::needsPerImageInstance ( ) const
inlinenodiscardoverridevirtualnoexcept

Whether NEB should create separate Potential instances per image for true parallel force evaluation.

When true, NEB calls makePotential() once per image instead of sharing one instance. Override in potentials that use internal mutexes (e.g. MetatomicPotential).

Reimplemented from eonc::Potential.

Definition at line 73 of file LAMMPSPot.h.

73 {
74 return true;
75 }

◆ noteScreenGeometry()

bool LAMMPSPot::noteScreenGeometry ( long N,
const double * box )
private

Definition at line 395 of file LAMMPSPot.cpp.

395 {
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}
bool screenHaveGeom_
Definition LAMMPSPot.h:159
long screenAtoms_
Definition LAMMPSPot.h:160
double screenBox_[9]
Definition LAMMPSPot.h:161

◆ runWorkerLoop()

void LAMMPSPot::runWorkerLoop ( )
private

Definition at line 295 of file LAMMPSPot.cpp.

295 {
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}
void setFixedMask(long nAtoms, const double *isFixed) override
Optional frozen-atom mask (nAtoms*3, 1.0 = fixed).
Definition LAMMPSPot.cpp:87

◆ setFixedMask()

void LAMMPSPot::setFixedMask ( long nAtoms,
const double * isFixed )
overridevirtual

Optional frozen-atom mask (nAtoms*3, 1.0 = fixed).

Default no-op. Matter calls this immediately before force() so wrappers that need it (LAMMPS setforce) see Atom.fixed without changing force().

Reimplemented from eonc::Potential.

Definition at line 87 of file LAMMPSPot.cpp.

87 {
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}

◆ stopWorker()

void LAMMPSPot::stopWorker ( )
private

Definition at line 350 of file LAMMPSPot.cpp.

350 {
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}
bool lammpsWorkerReaped(long got, long child, int err)
True when waitpid has collected the child. EINTR is not a collection.
Definition LAMMPSPot.h:31

Member Data Documentation

◆ fixedMask_

std::vector<double> LAMMPSPot::fixedMask_
private

Definition at line 121 of file LAMMPSPot.h.

◆ haveStress_

bool LAMMPSPot::haveStress_ {false}
private

Definition at line 120 of file LAMMPSPot.h.

120{false};

◆ lammpsLogging_

bool LAMMPSPot::lammpsLogging_ {false}
private

Definition at line 97 of file LAMMPSPot.h.

97{false};

◆ lammpsLogIndex_

int LAMMPSPot::lammpsLogIndex_ {0}
private

Definition at line 98 of file LAMMPSPot.h.

98{0};

◆ LAMMPSObj

void* LAMMPSPot::LAMMPSObj {nullptr}
private

Definition at line 114 of file LAMMPSPot.h.

114{nullptr};

◆ lammpsScreenPath_

std::string LAMMPSPot::lammpsScreenPath_
private

Definition at line 103 of file LAMMPSPot.h.

◆ lammpsScreenPos_

std::int64_t LAMMPSPot::lammpsScreenPos_ {0}
private

Definition at line 104 of file LAMMPSPot.h.

104{0};

◆ lammpsScreenRestart_

bool LAMMPSPot::lammpsScreenRestart_ {false}
private

Definition at line 107 of file LAMMPSPot.h.

107{false};

◆ lammpsThr

int LAMMPSPot::lammpsThr {0}
private

Definition at line 96 of file LAMMPSPot.h.

96{0};

◆ loader_

eonc::ILammpsLoader& LAMMPSPot::loader_
private

Definition at line 95 of file LAMMPSPot.h.

◆ maskMutex_

std::mutex LAMMPSPot::maskMutex_
private

Definition at line 99 of file LAMMPSPot.h.

◆ maskN_

long LAMMPSPot::maskN_ {0}
private

Definition at line 122 of file LAMMPSPot.h.

122{0};

◆ numberOfAtoms

long LAMMPSPot::numberOfAtoms {0}
private

Definition at line 112 of file LAMMPSPot.h.

112{0};

◆ oldBox

double LAMMPSPot::oldBox[9] {}
private

Definition at line 113 of file LAMMPSPot.h.

113{};

◆ realunits

bool LAMMPSPot::realunits {false}
private

Definition at line 118 of file LAMMPSPot.h.

118{false};

◆ reqFd

int LAMMPSPot::reqFd {-1}
private

Definition at line 154 of file LAMMPSPot.h.

154{-1}; // parent writes requests here (child stdin side)

◆ resFd

int LAMMPSPot::resFd {-1}
private

Definition at line 155 of file LAMMPSPot.h.

155{-1}; // parent reads results here (child stdout side)

◆ screenAtoms_

long LAMMPSPot::screenAtoms_ {0}
private

Definition at line 160 of file LAMMPSPot.h.

160{0};

◆ screenBox_

double LAMMPSPot::screenBox_[9] {}
private

Definition at line 161 of file LAMMPSPot.h.

161{};

◆ screenHaveGeom_

bool LAMMPSPot::screenHaveGeom_ {false}
private

Definition at line 159 of file LAMMPSPot.h.

159{false};

◆ stress_

Matrix3d LAMMPSPot::stress_ {Matrix3d::Zero()}
private

Definition at line 119 of file LAMMPSPot.h.

119{Matrix3d::Zero()};

◆ workerChild_

bool LAMMPSPot::workerChild_ {false}
private

Definition at line 108 of file LAMMPSPot.h.

108{false};

◆ workerMutex

std::mutex LAMMPSPot::workerMutex
private

Definition at line 126 of file LAMMPSPot.h.

◆ workerPid

int LAMMPSPot::workerPid {-1}
private

Definition at line 153 of file LAMMPSPot.h.

153{-1};

◆ workerRespawnsLeft

int LAMMPSPot::workerRespawnsLeft {3}
private

Definition at line 144 of file LAMMPSPot.h.

144{3};

◆ workerSpawned

bool LAMMPSPot::workerSpawned {false}
private

Definition at line 156 of file LAMMPSPot.h.

156{false};

The documentation for this class was generated from the following files:
  • /home/runner/work/eOn/eOn/include/eon/potentials/LAMMPS/LAMMPSPot.h
  • /home/runner/work/eOn/eOn/client/potentials/LAMMPS/LAMMPSPot.cpp