eOn 3.2.0
Long-timescale dynamics: aKMC, NEB, parallel replica
☾
Toggle main menu visibility
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
"
37
#include "
eon/potentials/VASP/VASP.h
"
38
39
namespace
{
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.
43
constexpr
const
char
*kVaspScript =
"runvasp.sh"
;
44
constexpr
const
char
*kForceFile =
"FU"
;
45
constexpr
const
char
*kNewCarFile =
"NEWCAR"
;
46
constexpr
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.
54
constexpr
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.
62
constexpr
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.
67
using
SpeciesRun = std::pair<int, long>;
68
76
std::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
98
bool
VASP::firstRun
=
true
;
99
long
VASP::vaspRunCount
= 0;
100
pid_t
VASP::vaspPID
= 0;
101
102
void
VASP::clearHandshakeFiles
() {
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
114
void
VASP::removeStaleFiles
() {
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
126
void
VASP::cleanMemory
(
void
) {
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
144
void
VASP::spawnVASP
() {
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.
148
clearHandshakeFiles
();
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
197
bool
VASP::vaspRunning
() {
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
219
void
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
252
void
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
312
void
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
}
EonLogger.h
EONC_LOG_WARNING
#define EONC_LOG_WARNING(...)
Definition
EonLogger.h:255
EONC_LOG_INFO
#define EONC_LOG_INFO(...)
Definition
EonLogger.h:249
VASP.h
VASP::spawnVASP
void spawnVASP()
Definition
VASP.cpp:144
VASP::vaspRunCount
static long vaspRunCount
Definition
VASP.h:54
VASP::clearHandshakeFiles
static void clearHandshakeFiles()
Definition
VASP.cpp:102
VASP::force
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
Definition
VASP.cpp:219
VASP::writePOSCAR
void writePOSCAR(long N, const double *R, const int *atomicNrs, const double *box)
Definition
VASP.cpp:252
VASP::cleanMemory
void cleanMemory()
Definition
VASP.cpp:126
VASP::firstRun
static bool firstRun
Definition
VASP.h:53
VASP::removeStaleFiles
static void removeStaleFiles()
Definition
VASP.cpp:114
VASP::readFU
void readFU(long N, double *F, double *U)
Definition
VASP.cpp:312
VASP::vaspRunning
bool vaspRunning()
Definition
VASP.cpp:197
VASP::vaspPID
static pid_t vaspPID
Definition
VASP.h:55
client
potentials
VASP
VASP.cpp
Generated by
1.17.0
Generated by
Doxygen 1.17.0
Analytics by
Antics
provided by
TurtleTech ehf