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