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