eOn client
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 <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
"
39
#include "
eon/potentials/VASP/VASP.h
"
40
41
namespace
{
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.
45
constexpr
const
char
*kVaspScript =
"runvasp.sh"
;
46
constexpr
const
char
*kForceFile =
"FU"
;
47
constexpr
const
char
*kNewCarFile =
"NEWCAR"
;
48
constexpr
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.
56
constexpr
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.
64
constexpr
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.
69
using
SpeciesRun = std::pair<int, long>;
70
78
std::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
100
bool
VASP::firstRun
=
true
;
101
long
VASP::vaspRunCount
= 0;
102
pid_t
VASP::vaspPID
= 0;
103
104
void
VASP::clearHandshakeFiles
() {
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
116
void
VASP::removeStaleFiles
() {
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
128
void
VASP::cleanMemory
(
void
) {
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
146
void
VASP::spawnVASP
() {
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.
150
clearHandshakeFiles
();
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
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
207
if
(pid) {
208
fprintf(stderr,
"vasp died unexpectedly!\n"
);
209
exit(1);
210
}
211
212
return
true
;
213
}
214
215
void
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
246
void
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
306
void
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
}
EonLogger.h
EONC_LOG_WARNING
#define EONC_LOG_WARNING(...)
Definition
EonLogger.h:256
EONC_LOG_INFO
#define EONC_LOG_INFO(...)
Definition
EonLogger.h:250
VASP.h
VASP::spawnVASP
void spawnVASP()
Definition
VASP.cpp:146
VASP::vaspRunCount
static long vaspRunCount
Definition
VASP.h:57
VASP::clearHandshakeFiles
static void clearHandshakeFiles()
Definition
VASP.cpp:104
VASP::writePOSCAR
void writePOSCAR(long N, const double *R, const int *atomicNrs, const double *box)
Definition
VASP.cpp:246
VASP::firstRun
static bool firstRun
Definition
VASP.h:56
VASP::force
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box)
Definition
VASP.cpp:215
VASP::cleanMemory
void cleanMemory(void)
Definition
VASP.cpp:128
VASP::removeStaleFiles
static void removeStaleFiles()
Definition
VASP.cpp:116
VASP::readFU
void readFU(long N, double *F, double *U)
Definition
VASP.cpp:306
VASP::vaspRunning
bool vaspRunning()
Definition
VASP.cpp:197
VASP::vaspPID
static pid_t vaspPID
Definition
VASP.h:58
client
potentials
VASP
VASP.cpp
Generated by
1.17.0
Generated by
Doxygen 1.17.0
Analytics by
Antics
provided by
TurtleTech ehf