Loading...
Searching...
No Matches
VASP.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
13#include <cerrno>
14#include <cstddef>
15#include <cstdio>
16#include <cstring>
17#include <filesystem>
18#include <format>
19#include <fstream>
20#include <stdexcept>
21#include <string>
22#include <system_error>
23#include <utility>
24#include <vector>
25
26#ifdef _WIN32
27#include <windows.h>
28#define sleep(n) Sleep(1000 * n)
29// #define popen _popen
30#else
31#include <fcntl.h>
32#include <sys/wait.h>
33#include <unistd.h>
34#endif
35
36#include "eon/EonLogger.h"
38
39namespace {
40
41// The driver script, and the files VASP and eOn hand back and forth. All of
42// them resolve against the working directory of the calling process.
43constexpr const char *kVaspScript = "runvasp.sh";
44constexpr const char *kForceFile = "FU";
45constexpr const char *kNewCarFile = "NEWCAR";
46constexpr const char *kStopCarFile = "STOPCAR";
47
48// The interactive handshake: eOn writes POSCAR and touches NEWCAR, VASP
49// answers with FU, and eOn writes STOPCAR to end the run. Each of these left
50// behind by an earlier client in the same directory breaks the next one, so
51// a run clears them before starting VASP. A stale FU is read as this run's
52// first forces, a stale NEWCAR feeds VASP a POSCAR eOn has not written yet,
53// and a stale STOPCAR aborts VASP on its first ionic step.
54constexpr const char *kHandshakeFiles[] = {kForceFile, kNewCarFile,
55 kStopCarFile};
56
57// Results and restart data VASP writes. VASP overwrites every one of them on
58// its next run and eOn reads none of them, so removing them buys a
59// calculation nothing; it discards the record of the previous one, and with
60// WAVECAR, CHGCAR and TMPCAR the wavefunction and charge density a restart
61// reads under ISTART and ICHARG.
62constexpr const char *kStaleFiles[] = {
63 "TMPCAR", "CHG", "CHGCAR", "CONTCAR", "DOSCAR", "EIGENVAL",
64 "IBZKPT", "OSZICAR", "OUTCAR", "PCDAT", "WAVECAR", "XDATCAR"};
65
66// A species and how many atoms it covers.
67using SpeciesRun = std::pair<int, long>;
68
76std::vector<SpeciesRun> speciesRuns(long N, const int *atomicNrs) {
77 std::vector<SpeciesRun> runs;
78 for (long i = 0; i < N; i++) {
79 if (!runs.empty() && runs.back().first == atomicNrs[i]) {
80 runs.back().second++;
81 continue;
82 }
83 for (const auto &run : runs) {
84 if (run.first == atomicNrs[i]) {
85 throw std::runtime_error(std::format(
86 "A POSCAR needs each species in one contiguous run, but atomic "
87 "number {} appears again at atom {}",
88 atomicNrs[i], i));
89 }
90 }
91 runs.emplace_back(atomicNrs[i], 1);
92 }
93 return runs;
94}
95
96} // namespace
97
98bool VASP::firstRun = true;
99long VASP::vaspRunCount = 0;
100pid_t VASP::vaspPID = 0;
101
103 for (const char *name : kHandshakeFiles) {
104 std::error_code ec;
105 if (std::filesystem::remove(name, ec)) {
106 EONC_LOG_INFO("VASP cleared leftover {}", name);
107 } else if (ec) {
108 EONC_LOG_WARNING("VASP could not clear leftover {}: {}", name,
109 ec.message());
110 }
111 }
112}
113
115 for (const char *name : kStaleFiles) {
116 std::error_code ec;
117 if (std::filesystem::remove(name, ec)) {
118 EONC_LOG_INFO("VASP removed leftover {}", name);
119 } else if (ec) {
120 EONC_LOG_WARNING("VASP could not remove leftover {}: {}", name,
121 ec.message());
122 }
123 }
124}
125
127 vaspRunCount--;
128 if (vaspRunCount < 1) {
129 // Runs from a destructor, so it reports rather than throws.
130 std::ofstream stopcar(kStopCarFile, std::ios::trunc);
131 if (!stopcar) {
132 EONC_LOG_WARNING("Could not open {} to stop VASP", kStopCarFile);
133 return;
134 }
135 stopcar << "LABORT = .TRUE.\n";
136 stopcar.close();
137 if (!stopcar) {
138 EONC_LOG_WARNING("Could not write {} to stop VASP", kStopCarFile);
139 }
140 }
141 return;
142}
143
145 // Runs once per client, since vaspPID stays set for the life of the
146 // process. The POSCAR eOn just wrote stays, and the first call writes no
147 // NEWCAR, so nothing here discards a signal this run has sent.
149
150 // execlp resolves a relative path against whatever directory the process
151 // last changed to, which under the MPI dispatcher is not necessarily the
152 // one holding the run. Resolve it in the parent, where a failure can still
153 // be reported.
154 std::error_code ec;
155 const std::filesystem::path script =
156 std::filesystem::absolute(kVaspScript, ec);
157 if (ec) {
158 throw std::runtime_error(
159 std::format("Could not resolve {}: {}", kVaspScript, ec.message()));
160 }
161 if (!std::filesystem::exists(script, ec) || ec) {
162 throw std::runtime_error(
163 std::format("{} does not exist; VASP needs it in the working directory",
164 script.string()));
165 }
166 const std::string scriptPath = script.string();
167
168 if ((vaspPID = fork()) == -1) {
169 throw std::runtime_error(
170 std::format("error forking for vasp: {}", std::strerror(errno)));
171 }
172
173 if (vaspPID != 0) {
174 setvbuf(stdout, nullptr, _IONBF, 0);
175 } else {
176 // The child must not throw: that would run the parent's destructors.
177 int outFd = open("vaspout", O_CREAT | O_WRONLY | O_TRUNC, 0644);
178 if (outFd == -1) {
179 fprintf(stderr, "error opening vaspout: %s\n", strerror(errno));
180 _exit(1);
181 }
182 if (dup2(outFd, 1) == -1 || dup2(outFd, 2) == -1) {
183 fprintf(stderr, "error redirecting vasp output: %s\n", strerror(errno));
184 _exit(1);
185 }
186 if (outFd > 2) {
187 close(outFd);
188 }
189 execl(scriptPath.c_str(), scriptPath.c_str(), nullptr);
190 // Only reached when the exec failed; _exit keeps the child out of the
191 // parent's exit handlers.
192 fprintf(stderr, "error spawning vasp: %s\n", strerror(errno));
193 _exit(1);
194 }
195}
196
198 pid_t pid;
199 int status;
200
201 if (vaspPID == 0) {
202 return false;
203 }
204
205 pid = waitpid(vaspPID, &status, WNOHANG);
206 // EINTR means the child is still running. Treating it as death aborts a
207 // live VASP job.
208 if (pid < 0 && errno == EINTR) {
209 return true;
210 }
211
212 if (pid != 0) {
213 throw std::runtime_error("vasp died unexpectedly");
214 }
215
216 return true;
217}
218
219void VASP::force(long N, const double *R, const int *atomicNrs, double *F,
220 double *U, double *variance, const double *box) {
221 if (variance != nullptr) {
222 *variance = 0.0;
223 }
224 writePOSCAR(N, R, atomicNrs, box);
225
226 if (!vaspRunning()) {
227 spawnVASP();
228 }
229
230 // access() succeeds the moment VASP creates FU, not when it finishes
231 // writing it, so this can observe a partial file. readFU treats a short
232 // read as an error rather than absorbing it, which turns the race into a
233 // failure instead of wrong forces. Closing it properly needs a completion
234 // signal from runvasp.sh, such as writing to a temporary name and renaming
235 // it once the write completes.
236 while (access(kForceFile, F_OK) == -1) {
237 sleep(1);
238 vaspRunning();
239 }
240 readFU(N, F, U);
241
242 std::error_code ec;
243 if (!std::filesystem::remove(kForceFile, ec) || ec) {
244 // Leaving it behind means the next call reads this call's numbers.
245 throw std::runtime_error(std::format("Could not remove {}: {}", kForceFile,
246 ec ? ec.message() : "already gone"));
247 }
248 vaspRunCount++;
249 return;
250}
251
252void VASP::writePOSCAR(long N, const double *R, const int *atomicNrs,
253 const double *box) {
254 // Positions are scaled
255 const std::vector<SpeciesRun> runs = speciesRuns(N, atomicNrs);
256
257 std::ofstream poscar("POSCAR", std::ios::trunc);
258 if (!poscar) {
259 throw std::runtime_error("Could not open POSCAR for writing");
260 }
261
262 // header line (treated as a comment)
263 for (const auto &run : runs) {
264 poscar << std::format("{} ", run.first);
265 }
266 poscar << ": Atomic numbers\n";
267
268 // boundary box
269 poscar << "1.0\n";
270 for (int i = 0; i < 3; i++) {
271 poscar << std::format(" {:.8f}\t{:.8f}\t{:.8f}\n", box[i * 3 + 0],
272 box[i * 3 + 1], box[i * 3 + 2]);
273 }
274
275 // the number of atoms of each different atomic type
276 for (std::size_t i = 0; i < runs.size(); i++) {
277 poscar << std::format("{}{}", runs[i].second,
278 i + 1 == runs.size() ? "\n" : " ");
279 }
280
281 // coordinates for all atoms
282 poscar << "Cartesian\n";
283 for (long i = 0; i < N; i++) {
284 poscar << std::format("{:.19f}\t{:.19f}\t{:.19f}\t T T T\n", R[i * 3 + 0],
285 R[i * 3 + 1], R[i * 3 + 2]);
286 }
287
288 poscar.close();
289 if (!poscar) {
290 throw std::runtime_error("Could not write the structure to POSCAR");
291 }
292
293 if (firstRun) {
294 firstRun = false;
295 } else {
296 // An empty NEWCAR tells the running VASP that a new POSCAR is ready.
297 std::ofstream newcar(kNewCarFile, std::ios::trunc);
298 if (!newcar) {
299 throw std::runtime_error(
300 std::format("Could not open {} to signal VASP", kNewCarFile));
301 }
302 newcar.close();
303 if (!newcar) {
304 throw std::runtime_error(
305 std::format("Could not write {} to signal VASP", kNewCarFile));
306 }
307 }
308
309 return;
310}
311
312void VASP::readFU(long N, double *F, double *U) {
313 std::ifstream fu(kForceFile);
314 if (!fu) {
315 throw std::runtime_error(
316 std::format("Could not open {}; VASP left no result", kForceFile));
317 }
318
319 if (!(fu >> *U)) {
320 throw std::runtime_error(
321 std::format("Could not read the energy from {}", kForceFile));
322 }
323
324 for (long i = 0; i < N; i++) {
325 if (!(fu >> F[i * 3 + 0] >> F[i * 3 + 1] >> F[i * 3 + 2])) {
326 throw std::runtime_error(std::format(
327 "{} holds forces for {} atoms, expected {}", kForceFile, i, N));
328 }
329 }
330 return;
331}
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:255
#define EONC_LOG_INFO(...)
Definition EonLogger.h:249
void spawnVASP()
Definition VASP.cpp:144
static long vaspRunCount
Definition VASP.h:54
static void clearHandshakeFiles()
Definition VASP.cpp:102
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
Definition VASP.cpp:219
void writePOSCAR(long N, const double *R, const int *atomicNrs, const double *box)
Definition VASP.cpp:252
void cleanMemory()
Definition VASP.cpp:126
static bool firstRun
Definition VASP.h:53
static void removeStaleFiles()
Definition VASP.cpp:114
void readFU(long N, double *F, double *U)
Definition VASP.cpp:312
bool vaspRunning()
Definition VASP.cpp:197
static pid_t vaspPID
Definition VASP.h:55