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 Parameters &p)
 ~LAMMPSPot ()
void cleanMemory ()
void force (long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
Public Member Functions inherited from eonc::Potential
 Potential (PotType a_ptype)
 Potential (PotType a_ptype, const Parameters &)
 Potential (const Parameters &a_params)
virtual ~Potential ()
std::tuple< double, AtomMatrixget_ef (const AtomMatrix &pos, const VectorXi &atmnrs, const Matrix3d &box)
PotType getType () const
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 bool isSharedInstanceThreadSafe () const noexcept
 Conservative gate for sharing one Potential instance across threads.
virtual bool supportsBatchEvaluation () const noexcept
 Whether this potential supports batched evaluation of 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)
 Evaluate forces for N systems in a single call.

Private Member Functions

void makeNewLAMMPS (long N, const double *R, const int *atomicNrs, const double *box)
void ensureWorker ()
void runWorkerLoop ()
void stopWorker ()
void forceLocal (long N, const double *R, const int *atomicNrs, double *F, double *U, const double *box)

Private Attributes

int lammpsThr {0}
long numberOfAtoms {0}
double oldBox [9] {}
void * LAMMPSObj {nullptr}
bool realunits {false}
int workerRespawnsLeft {3}
std::mutex workerMutex
int workerPid {-1}
int reqFd {-1}
int resFd {-1}
bool workerSpawned {false}

Additional Inherited Members

Public Attributes inherited from eonc::Potential
std::atomic< size_t > forceCallCounter
Protected Attributes inherited from eonc::Potential
PotType ptype

Detailed Description

Definition at line 21 of file LAMMPSPot.h.

Constructor & Destructor Documentation

◆ LAMMPSPot()

LAMMPSPot::LAMMPSPot ( const Parameters & p)

Definition at line 40 of file LAMMPSPot.cpp.

41 : Potential(p),
43#ifdef EONMPI
44 ,
45 mpiComm{p.potential_options.MPIClientComm}
46#endif
47{
48 // Fail fast if LAMMPS library not available
50#if !defined(EONMPI) && !defined(IS_WINDOWS)
51 // Fork the worker NOW, at construction, before this process ever opens a
52 // LAMMPS instance (and thus before liblammps initialises MPI). Open MPI
53 // does not support using MPI in a process that called MPI_Init before fork,
54 // so the worker must be spawned from a still-MPI-clean parent. Every
55 // LAMMPSPot -- endpoints and per-image alike -- runs its LAMMPS in its own
56 // child process, so the parent never initialises MPI at all.
58#endif
59}
int lammpsThr
Definition LAMMPSPot.h:34
void ensureWorker()
void require_loaded()
Load on first use, then throw if liblammps is not available.
static LammpsLoader & instance()
Thread-safe singleton accessor (Meyer's pattern). Does not dlopen.
struct eonc::Parameters::potential_options_t potential_options
Potential(PotType a_ptype)
Definition Potential.h:35

◆ ~LAMMPSPot()

LAMMPSPot::~LAMMPSPot ( )

Definition at line 61 of file LAMMPSPot.cpp.

61{ cleanMemory(); }
void cleanMemory()
Definition LAMMPSPot.cpp:63

Member Function Documentation

◆ cleanMemory()

void LAMMPSPot::cleanMemory ( )

Definition at line 63 of file LAMMPSPot.cpp.

63 {
64#if !defined(EONMPI) && !defined(IS_WINDOWS)
65 stopWorker();
66#endif
67 if (LAMMPSObj != nullptr) {
69 LAMMPSObj = nullptr;
70 }
71}
void stopWorker()
void * LAMMPSObj
Definition LAMMPSPot.h:40

◆ ensureWorker()

void LAMMPSPot::ensureWorker ( )
private

Definition at line 140 of file LAMMPSPot.cpp.

140 {
141 if (workerSpawned)
142 return;
143
144 int reqPipe[2]; // parent -> child
145 int resPipe[2]; // child -> parent
146 if (pipe(reqPipe) != 0 || pipe(resPipe) != 0) {
147 throw std::runtime_error("LAMMPSPot: failed to create worker pipes");
148 }
149
150 // Fork BEFORE opening any LAMMPS instance in this process, so MPI is first
151 // initialised inside the child. Each child is its own process with its own
152 // MPI_COMM_WORLD; concurrent children never share a communicator.
153 pid_t pid = fork();
154 if (pid < 0) {
155 throw std::runtime_error("LAMMPSPot: fork for worker failed");
156 }
157
158 if (pid == 0) {
159 // Child: keep reqPipe read end and resPipe write end.
160 close(reqPipe[1]);
161 close(resPipe[0]);
162 reqFd = reqPipe[0];
163 resFd = resPipe[1];
164 // Client main arms feenableexcept unconditionally. The worker inherits
165 // that mask; LAMMPS EAM (PairEAM::compute) performs IEEE divisions that
166 // can raise FE_DIVBYZERO on near-coincident pairs during saddle / product
167 // minimisations. Under trapping that SIGFPEs, and the continue handler
168 // without MXCSR masking re-storms forever in the child (GB of identical
169 // "FPE (continuing)" lines). External pot code expects soft IEEE defaults;
170 // demote trapping for the whole worker process before any LAMMPS call.
172 runWorkerLoop(); // never returns
173 }
174
175 // Parent: keep reqPipe write end and resPipe read end.
176 //
177 // Writing to a worker that has already exited raises SIGPIPE, whose default
178 // action kills the client outright -- before writeExact can return the
179 // error the caller is written to handle. A search that had converged on a
180 // saddle at 0.062 eV died this way with signal 13 as its endpoints were
181 // about to be minimised. Ignoring it turns the same condition into an
182 // EPIPE return, which reaches the geometry-rejection path and respawns.
183 std::signal(SIGPIPE, SIG_IGN);
184 close(reqPipe[0]);
185 close(resPipe[1]);
186 reqFd = reqPipe[1];
187 resFd = resPipe[0];
188 workerPid = pid;
189 workerSpawned = true;
190}
int reqFd
Definition LAMMPSPot.h:72
int workerPid
Definition LAMMPSPot.h:71
bool workerSpawned
Definition LAMMPSPot.h:74
void runWorkerLoop()
int resFd
Definition LAMMPSPot.h:73
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 273 of file LAMMPSPot.cpp.

274 {
275 variance = nullptr;
276
277#ifdef EONMPI
278 forceLocal(N, R, atomicNrs, F, U, box);
279#elif defined(IS_WINDOWS)
280 // No fork/pipe on Windows; call forceLocal directly.
281 forceLocal(N, R, atomicNrs, F, U, box);
282#else
283 // Drive the dedicated worker process so this image's LAMMPS runs in its own
284 // process (own MPI_COMM_WORLD). Per-image NEB threads thus evaluate forces
285 // as truly concurrent processes with no shared-communicator contention.
286 std::lock_guard<std::mutex> workerLock(workerMutex);
287 if (workerRespawnsLeft <= 0) {
288 rejectGeometry(U, F, N);
289 return;
290 }
291 ensureWorker();
292
293 if (!writeExact(reqFd, &N, sizeof(N)) ||
294 !writeExact(reqFd, atomicNrs, sizeof(int) * static_cast<size_t>(N)) ||
295 !writeExact(reqFd, box, sizeof(double) * 9) ||
296 !writeExact(reqFd, R, sizeof(double) * static_cast<size_t>(3 * N))) {
297 // A worker stopped by an earlier rejected geometry leaves the request
298 // pipe closed, so the first send after it fails. Respawning happens on
299 // the next evaluation; reject this one rather than end the client.
301 EONC_LOG_WARNING("[LAMMPSPot] send to worker failed; {} respawns left "
302 "(eon-7416)",
304 stopWorker();
305 rejectGeometry(U, F, N);
306 return;
307 }
308
309 // eon-7416: a worker stuck on a pathological geometry (LAMMPS spinning, or
310 // a NaN it never returns) would make the blocking read below hang until the
311 // akmc pass times out. Bound the wait: if the worker is silent past a
312 // generous per-eval deadline, kill and reap it (a stuck worker never reaches
313 // EOF, so a plain waitpid would block too) and fail this evaluation so the
314 // search discards the geometry; ensureWorker respawns on the next call.
315 {
316 struct pollfd pfd;
317 pfd.fd = resFd;
318 pfd.events = POLLIN;
319 pfd.revents = 0;
320 int pr = poll(&pfd, 1, 90000); // 90 s: orders beyond a normal force eval
321 if (pr <= 0) {
322 if (workerPid > 0) {
323 kill(workerPid, SIGKILL);
324 }
327 "[LAMMPSPot] worker force eval timed out; {} respawns left "
328 "(eon-7416)",
330 stopWorker();
331 rejectGeometry(U, F, N);
332 return;
333 }
334 }
335 int status = 0;
336 if (!readExact(resFd, &status, sizeof(status)) ||
337 !readExact(resFd, U, sizeof(double)) ||
338 !readExact(resFd, F, sizeof(double) * static_cast<size_t>(3 * N))) {
341 "[LAMMPSPot] worker died during force eval; {} respawns left "
342 "(eon-7416)",
344 stopWorker();
345 rejectGeometry(U, F, N);
346 return;
347 }
348 if (status != 0) {
351 "[LAMMPSPot] worker reported an evaluation error; {} respawns left "
352 "(eon-7416)",
354 stopWorker();
355 rejectGeometry(U, F, N);
356 return;
357 }
358 // A saddle search that never terminates silently truncates the event
359 // table: the KMC residence time is 1/sum_j k_j over the discovered
360 // mechanisms, so a dropped search removes a term and biases the clock
361 // (Pedersen and Jónsson, Math. Comput. Simul. 80, 1487 (2010),
362 // doi:10.1016/j.matcom.2009.02.010, Fig. 1; Alexander and Schuh,
363 // Modelling Simul. Mater. Sci. Eng. 24, 065014 (2016),
364 // doi:10.1088/0965-0393/24/6/065014, on catalog completeness).
365 // An over-aggressive saddle-search kick can drive atoms on top of
366 // each other; the EAM force overflows to NaN/Inf and LAMMPS returns it
367 // rather than crashing. The min-mode search then spins on non-finite
368 // gradients until the akmc pass times out (0 processes). Reject the
369 // evaluation so the search discards that displacement and continues.
370 // Reject the geometry, not the process. Nothing between this call and
371 // main catches a throw here, so the client terminates: a search that was
372 // making progress is lost, and every other search sharing the pass goes
373 // with it. A large finite energy with zeroed forces reads to the
374 // optimiser as an impassable wall, so it backs out of the step and the
375 // search abandons this configuration and carries on.
376 bool nonfinite = !std::isfinite(*U);
377 for (long i = 0; i < 3 * N && !nonfinite; ++i) {
378 nonfinite = !std::isfinite(F[i]);
379 }
380 if (nonfinite) {
381 EONC_LOG_WARNING("[LAMMPSPot] non-finite force or energy; rejecting "
382 "geometry (eon-7416)");
383 rejectGeometry(U, F, N);
384 }
385#endif
386}
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:256
int workerRespawnsLeft
Definition LAMMPSPot.h:61
void forceLocal(long N, const double *R, const int *atomicNrs, double *F, double *U, const double *box)
std::mutex workerMutex
Definition LAMMPSPot.h:70

◆ forceLocal()

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

Definition at line 388 of file LAMMPSPot.cpp.

389 {
390 // Same contract as ASE / Metatomic: external pot libraries are not written
391 // for FE traps. Cover in-process (EONMPI / Windows) and any path that still
392 // has trapping armed when forceLocal runs. Always restore so FE traps do not
393 // stay demoted for the rest of the process after the first force call.
394 eonc::FPEHandler fpeh;
395 fpeh.eat_fpe();
396 try {
397 auto &lmp = eonc::LammpsLoader::instance();
398
399 bool newLammps = false;
400 for (int i = 0; i < 9; i++) {
401 if (oldBox[i] != box[i])
402 newLammps = true;
403 }
404 if (numberOfAtoms != N)
405 newLammps = true;
406 if (newLammps) {
407 makeNewLAMMPS(N, R, atomicNrs, box);
408 }
409 if (!LAMMPSObj) {
410 throw std::runtime_error("Should have a LAMMPS instance by now");
411 }
412
413 lmp.scatter_atoms(LAMMPSObj, "x", 1, 3, const_cast<double *>(R));
414 lmp.command(LAMMPSObj, "run 1 pre no post no");
415
416 auto *pe =
417 static_cast<double *>(lmp.extract_variable(LAMMPSObj, "pe", nullptr));
418 *U = *pe;
419 free(pe);
420
421 auto *fx =
422 static_cast<double *>(lmp.extract_variable(LAMMPSObj, "fx", "all"));
423 auto *fy =
424 static_cast<double *>(lmp.extract_variable(LAMMPSObj, "fy", "all"));
425 auto *fz =
426 static_cast<double *>(lmp.extract_variable(LAMMPSObj, "fz", "all"));
427
428 for (long i = 0; i < N; i++) {
429 F[3 * i + 0] = fx[i];
430 F[3 * i + 1] = fy[i];
431 F[3 * i + 2] = fz[i];
432 }
433
434 // Convert kCal/mol -> eV if LAMMPS is using real units
435 if (realunits) {
436 constexpr double kcalPerEv = 23.0609;
437 *U /= kcalPerEv;
438 for (long i = 0; i < 3 * N; i++) {
439 F[i] /= kcalPerEv;
440 }
441 }
442
443 free(fx);
444 free(fy);
445 free(fz);
446 } catch (...) {
447 fpeh.restore_fpe();
448 throw;
449 }
450 fpeh.restore_fpe();
451}
double oldBox[9]
Definition LAMMPSPot.h:39
void makeNewLAMMPS(long N, const double *R, const int *atomicNrs, const double *box)
bool realunits
Definition LAMMPSPot.h:43
long numberOfAtoms
Definition LAMMPSPot.h:38

◆ makeNewLAMMPS()

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

Definition at line 453 of file LAMMPSPot.cpp.

454 {
455 auto &lmp = eonc::LammpsLoader::instance();
456
457 numberOfAtoms = N;
458 std::memcpy(oldBox, box, 9 * sizeof(double));
459
460 if (LAMMPSObj != nullptr) {
462 LAMMPSObj = nullptr;
463 }
464
465 // Map atomic numbers to LAMMPS type indices (1-based)
466 std::map<int, int> type_map;
467 int ntypes = 0;
468 for (long i = 0; i < N; i++) {
469 if (type_map.count(atomicNrs[i]) == 0) {
470 type_map.insert({atomicNrs[i], ++ntypes});
471 }
472 }
473
474#ifdef EONMPI
475 const char *lmpargv[] = {"liblammps", "-log", "none", "-echo", "log",
476 "-screen", "none", "-suffix", "omp"};
477 int lmpargc = sizeof(lmpargv) / sizeof(const char *);
478 if (!lmp.open_mpi) {
479 throw std::runtime_error(
480 "LAMMPS library found but lacks MPI support (lammps_open not found).\n"
481 "Install an MPI-enabled LAMMPS build.");
482 }
483 MPI_Comm inst_comm = MPI_COMM_NULL;
484 MPI_Comm_dup(mpiComm, &inst_comm); // private comm per per-image instance
485 LAMMPSObj =
486 lmp.open_mpi(lmpargc, const_cast<char **>(lmpargv), inst_comm, nullptr);
487#else
488 const char *lmpargv[] = {"liblammps", "-log", "none", "-echo",
489 "log", "-screen", "none"};
490 int lmpargc = sizeof(lmpargv) / sizeof(const char *);
491 LAMMPSObj = lmp.open_no_mpi(lmpargc, const_cast<char **>(lmpargv), nullptr);
492#endif
493
494 if (lammpsThr > 0) {
495 std::string cmd = std::format("package omp {} force/neigh", lammpsThr);
496 lmp.command(LAMMPSObj, cmd.c_str());
497 }
498
499 // Detect units from in.lammps: look for "#!units real" marker
500 realunits = false;
501 if (std::filesystem::exists("in.lammps")) {
502 std::ifstream infile("in.lammps");
503 std::string line;
504 while (std::getline(infile, line)) {
505 if (line == "#!units real") {
506 realunits = true;
507 break;
508 }
509 }
510 } else {
511 EONC_LOG_ERROR("[LAMMPS] in.lammps not found in working directory");
512 return;
513 }
514
515 if (realunits) {
516 lmp.command(LAMMPSObj, "units real");
517 } else {
518 lmp.command(LAMMPSObj, "units metal");
519 }
520
521 lmp.command(LAMMPSObj, "atom_style charge");
522 lmp.command(LAMMPSObj, "atom_modify map array sort 0 0");
523 lmp.command(LAMMPSObj, "neigh_modify delay 1");
524
525 // Define periodic cell (prism for non-orthorhombic)
526 std::string region_cmd =
527 std::format("region cell prism 0 {} 0 {} 0 {} {} {} {} units box", box[0],
528 box[4], box[8], box[3], box[6], box[7]);
529 lmp.command(LAMMPSObj, region_cmd.c_str());
530
531 std::string create_box_cmd = std::format("create_box {} cell", ntypes);
532 lmp.command(LAMMPSObj, create_box_cmd.c_str());
533
534 // Initialize atoms
535 for (long i = 0; i < N; i++) {
536 std::string atom_cmd =
537 std::format("create_atoms {} single {} {} {} units box",
538 type_map[atomicNrs[i]], 0.0, 0.0, 0.0);
539 lmp.command(LAMMPSObj, atom_cmd.c_str());
540 }
541
542 lmp.command(LAMMPSObj, "mass * 1.0");
543
544 // Load user LAMMPS input script
545 lmp.file(LAMMPSObj, "in.lammps");
546
547 // Define variables for force/energy extraction
548 lmp.command(LAMMPSObj, "variable fx atom fx");
549 lmp.command(LAMMPSObj, "variable fy atom fy");
550 lmp.command(LAMMPSObj, "variable fz atom fz");
551 lmp.command(LAMMPSObj, "variable pe equal pe");
552}
#define EONC_LOG_ERROR(...)
Definition EonLogger.h:262

◆ 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 24 of file LAMMPSPot.h.

24 {
25 return true;
26 }

◆ runWorkerLoop()

void LAMMPSPot::runWorkerLoop ( )
private

Definition at line 192 of file LAMMPSPot.cpp.

192 {
193 // Running in the forked child. Evaluate forces with an in-process LAMMPS
194 // (this child's own MPI_COMM_WORLD) and stream results back to the parent.
195 for (;;) {
196 long N = 0;
197 if (!readExact(reqFd, &N, sizeof(N))) {
198 _exit(0); // request pipe closed -> shut down cleanly
199 }
200 if (N < 0) {
201 _exit(0); // explicit shutdown sentinel from stopWorker()
202 }
203 std::vector<int> atomicNrs(static_cast<size_t>(N));
204 std::vector<double> R(static_cast<size_t>(3 * N));
205 double box[9];
206 if (!readExact(reqFd, atomicNrs.data(),
207 sizeof(int) * static_cast<size_t>(N)) ||
208 !readExact(reqFd, box, sizeof(box)) ||
209 !readExact(reqFd, R.data(),
210 sizeof(double) * static_cast<size_t>(3 * N))) {
211 _exit(1);
212 }
213
214 std::vector<double> F(static_cast<size_t>(3 * N), 0.0);
215 double U = 0.0;
216 int status = 0;
217 try {
218 forceLocal(N, R.data(), atomicNrs.data(), F.data(), &U, box);
219 } catch (...) {
220 status = 1;
221 }
222
223 if (!writeExact(resFd, &status, sizeof(status)) ||
224 !writeExact(resFd, &U, sizeof(U)) ||
225 !writeExact(resFd, F.data(),
226 sizeof(double) * static_cast<size_t>(3 * N))) {
227 _exit(1);
228 }
229 }
230}

◆ stopWorker()

void LAMMPSPot::stopWorker ( )
private

Definition at line 232 of file LAMMPSPot.cpp.

232 {
233 if (!workerSpawned)
234 return;
235 if (reqFd >= 0) {
236 // Send an explicit shutdown sentinel, then close. A sentinel (rather than
237 // relying on pipe EOF) guarantees the child exits even when sibling worker
238 // processes hold an inherited copy of this write end.
239 long sentinel = -1;
240 writeExact(reqFd, &sentinel, sizeof(sentinel));
241 close(reqFd);
242 reqFd = -1;
243 }
244 if (resFd >= 0) {
245 close(resFd);
246 resFd = -1;
247 }
248 if (workerPid > 0) {
249 // A wedged worker never acts on the sentinel or the closed pipe, and an
250 // unconditional wait then blocks the client forever at no CPU cost -- the
251 // hang this teardown exists to avoid. Give the child a brief chance to
252 // exit on its own, then insist.
253 int st = 0;
254 bool reaped = false;
255 for (int i = 0; i < 100; ++i) { // up to ~1 s
256 pid_t r = waitpid(workerPid, &st, WNOHANG);
257 if (r == workerPid || r < 0) {
258 reaped = true;
259 break;
260 }
261 usleep(10000);
262 }
263 if (!reaped) {
264 kill(workerPid, SIGKILL);
265 waitpid(workerPid, &st, 0);
266 }
267 workerPid = -1;
268 }
269 workerSpawned = false;
270}

Member Data Documentation

◆ LAMMPSObj

void* LAMMPSPot::LAMMPSObj {nullptr}
private

Definition at line 40 of file LAMMPSPot.h.

40{nullptr};

◆ lammpsThr

int LAMMPSPot::lammpsThr {0}
private

Definition at line 34 of file LAMMPSPot.h.

34{0};

◆ numberOfAtoms

long LAMMPSPot::numberOfAtoms {0}
private

Definition at line 38 of file LAMMPSPot.h.

38{0};

◆ oldBox

double LAMMPSPot::oldBox[9] {}
private

Definition at line 39 of file LAMMPSPot.h.

39{};

◆ realunits

bool LAMMPSPot::realunits {false}
private

Definition at line 43 of file LAMMPSPot.h.

43{false};

◆ reqFd

int LAMMPSPot::reqFd {-1}
private

Definition at line 72 of file LAMMPSPot.h.

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

◆ resFd

int LAMMPSPot::resFd {-1}
private

Definition at line 73 of file LAMMPSPot.h.

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

◆ workerMutex

std::mutex LAMMPSPot::workerMutex
private

Definition at line 70 of file LAMMPSPot.h.

◆ workerPid

int LAMMPSPot::workerPid {-1}
private

Definition at line 71 of file LAMMPSPot.h.

71{-1};

◆ workerRespawnsLeft

int LAMMPSPot::workerRespawnsLeft {3}
private

Definition at line 61 of file LAMMPSPot.h.

61{3};

◆ workerSpawned

bool LAMMPSPot::workerSpawned {false}
private

Definition at line 74 of file LAMMPSPot.h.

74{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