Loading...
Searching...
No Matches
ExtPot.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*/
12
14#include "eon/Parameters.h"
17
18#include <atomic>
19#include <filesystem>
20#include <format>
21#include <fstream>
22#include <stdexcept>
23#include <string>
24#include <system_error>
25
26#ifdef _WIN32
27#ifndef WIN32_LEAN_AND_MEAN
28#define WIN32_LEAN_AND_MEAN
29#endif
30#ifndef NOMINMAX
31#define NOMINMAX
32#endif
33#include <process.h>
34#include <windows.h>
35#else
36#include <unistd.h>
37#endif
38
39namespace {
40
41// The names the external program reads and writes. They are part of the
42// documented interface and stay fixed; the directory holding them is what
43// varies from one client to the next.
44constexpr const char *kToExtPot = "from_eon_to_extpot";
45constexpr const char *kFromExtPot = "from_extpot_to_eon";
46
47// Names the directory eOn was started in for a wrapper that needs to reach
48// back to it.
49constexpr const char *kRunDirVariable = "EON_EXTPOT_RUN_DIR";
50
51long processId() {
52#ifdef _WIN32
53 return static_cast<long>(_getpid());
54#else
55 return static_cast<long>(getpid());
56#endif
57}
58
59#ifdef _WIN32
64std::string windowsPythonCommand() {
65 char buf[MAX_PATH];
66 const DWORD n =
67 SearchPathA(nullptr, "python.exe", nullptr, MAX_PATH, buf, nullptr);
68 if (n > 0 && n < MAX_PATH) {
69 return eonc::pot::shellQuote(std::string(buf));
70 }
71 return "python";
72}
73#endif
74
81std::string composeCommand(const std::string &workDir,
82 const std::string &runDir,
83 const std::string &command) {
84#ifdef _WIN32
85 const std::string quotedCommand =
86 eonc::pot::wrapResolvedProgram(command, windowsPythonCommand());
87 // cmd.exe: quotes around name=value so the value does not include them.
88 return std::format("cd /d {} && set \"{}={}\" && {}",
89 eonc::pot::shellQuote(workDir), kRunDirVariable, runDir,
90 quotedCommand);
91#else
92 const std::string quotedCommand = eonc::pot::wrapResolvedProgram(command, {});
93 return std::format("cd {} && export {}={} && {}",
94 eonc::pot::shellQuote(workDir), kRunDirVariable,
95 eonc::pot::shellQuote(runDir), quotedCommand);
96#endif
97}
98
106std::string resolveCommand(const std::string &command) {
107 std::error_code ec;
108 const std::filesystem::path named{command};
109 if (!std::filesystem::is_regular_file(named, ec) || ec) {
110 return command;
111 }
112 const std::filesystem::path resolved = std::filesystem::absolute(named, ec);
113 if (ec) {
114 return command;
115 }
116 return resolved.string();
117}
118
119} // namespace
120
122 : eonc::Potential(p),
123 eon_extpot_path{resolveCommand(p.potential_options().extPotPath)} {}
124
126 if (exchangeDir.empty() || retainExchangeDir) {
127 return;
128 }
129 std::error_code ec;
130 std::filesystem::remove(exchangeDir / kToExtPot, ec);
131 std::filesystem::remove(exchangeDir / kFromExtPot, ec);
132 // Removes the directory only when it is empty, so whatever else the
133 // external program wrote there stays.
134 std::filesystem::remove(exchangeDir, ec);
135}
136
138
140 std::error_code ec;
141 if (exchangeDir.empty()) {
142 // One directory per potential object. A client started alongside this
143 // one carries a different process id, and a second potential in this
144 // process a different index, so no two of them name the same file.
145 static std::atomic<long> instanceCount{0};
146 const std::filesystem::path named{
147 std::format("extpot_{}_{}", processId(), instanceCount.fetch_add(1))};
148 exchangeDir = std::filesystem::absolute(named, ec);
149 if (ec) {
150 exchangeDir.clear();
151 throw std::runtime_error(std::format("Could not resolve {}: {}",
152 named.string(), ec.message()));
153 }
154 }
155 std::filesystem::create_directory(exchangeDir, ec);
156 if (ec) {
157 throw std::runtime_error(std::format("Could not create {} to run {} in: {}",
159 ec.message()));
160 }
161 // A result left by an earlier call, or by a client that died holding this
162 // process id, is read as this call's forces when the external program
163 // writes nothing.
164 std::filesystem::remove(exchangeDir / kFromExtPot, ec);
165}
166
167void ExtPot::force(long N, const double *R, const int *atomicNrs, double *F,
168 double *U, double *variance, const double *box) {
169 if (variance != nullptr) {
170 *variance = 0.0;
171 }
172 if (eon_extpot_path.empty()) {
173 throw std::runtime_error(
174 "ExtPot needs potential_options.extPotPath to name a program to run");
175 }
177 retainExchangeDir = true;
178 passToSystem(N, R, atomicNrs, box);
179 std::error_code ec;
180 const std::filesystem::path runDir = std::filesystem::current_path(ec);
181 eonc::pot::runOrThrow(composeCommand(exchangeDir.string(),
182 ec ? std::string{} : runDir.string(),
184 recieveFromSystem(N, F, U);
185 retainExchangeDir = false;
186 return;
187}
188
189void ExtPot::passToSystem(long N, const double *R, const int *atomicNrs,
190 const double *box)
191// 'positions' of all particles and box
192{
193 const std::filesystem::path toExtPot = exchangeDir / kToExtPot;
194 std::ofstream out(toExtPot, std::ios::trunc);
195 if (!out) {
196 throw std::runtime_error(
197 std::format("Could not open {} for writing", toExtPot.string()));
198 }
199
200 for (int i = 0; i < 3; i++) {
201 out << std::format("{:.19f}\t{:.19f}\t{:.19f}\n", box[i * 3 + 0],
202 box[i * 3 + 1], box[i * 3 + 2]);
203 }
204
205 for (long i = 0; i < N; i++) {
206 out << std::format("{}\t{:.19f}\t{:.19f}\t{:.19f}\n", atomicNrs[i],
207 R[i * 3 + 0], R[i * 3 + 1], R[i * 3 + 2]);
208 }
209
210 out.close();
211 if (!out) {
212 throw std::runtime_error(
213 std::format("Could not write the structure to {}", toExtPot.string()));
214 }
215 return;
216}
217
218void ExtPot::recieveFromSystem(long N, double *F, double *U)
219// first line must be the total 'energy', the following lines should be the
220// 'forces'
221{
222 const std::filesystem::path fromExtPot = exchangeDir / kFromExtPot;
223 std::ifstream in(fromExtPot);
224 if (!in) {
225 throw std::runtime_error(
226 std::format("Could not open {}; the external program left no result",
227 fromExtPot.string()));
228 }
229
230 if (!(in >> *U)) {
231 throw std::runtime_error(
232 std::format("Could not read the energy from {}", fromExtPot.string()));
233 }
234
235 for (long i = 0; i < N; i++) {
236 if (!(in >> F[i * 3 + 0] >> F[i * 3 + 1] >> F[i * 3 + 2])) {
237 throw std::runtime_error(
238 std::format("{} holds forces for {} atoms, expected {}",
239 fromExtPot.string(), i, N));
240 }
241 }
242 return;
243}
void recieveFromSystem(long N, double *F, double *U)
Definition ExtPot.cpp:218
void prepareExchangeDir()
Definition ExtPot.cpp:139
~ExtPot()
Definition ExtPot.cpp:137
void cleanMemory(void)
Definition ExtPot.cpp:125
std::string eon_extpot_path
Definition ExtPot.h:50
bool retainExchangeDir
Definition ExtPot.h:57
ExtPot(const eonc::Parameters &p)
Definition ExtPot.cpp:121
std::filesystem::path exchangeDir
Definition ExtPot.h:54
void passToSystem(long N, const double *R, const int *atomicNrs, const double *box)
Definition ExtPot.cpp:189
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
Definition ExtPot.cpp:167
Potential(PotType a_ptype)
Production default: construction-scope registry, else PotRegistry::get().
void runOrThrow(const std::string &command)
Run a shell command, throwing when it does not succeed.
std::string shellQuote(const std::string &text)
Wrap a path for the shell system() hands the command to, so spaces in it survive.
std::string wrapResolvedProgram(const std::string &command, const std::string &python)
Quote a resolved program path, and on Windows prefix a script so cmd.exe can start it.
RAII resource manager for the ARTn C library with global synchronization.