-
Notifications
You must be signed in to change notification settings - Fork 41
/
qmof.py
352 lines (305 loc) · 9.89 KB
/
qmof.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
"""
QMOF-compatible recipes.
This set of recipes is meant to be compatible with the QMOF Database workflow.
Reference: https://doi.org/10.1016/j.matt.2021.02.015
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from ase.optimize import BFGSLineSearch
from quacc import change_settings, job
from quacc.recipes.vasp._base import run_and_summarize, run_and_summarize_opt
if TYPE_CHECKING:
from ase.atoms import Atoms
from quacc.schemas._aliases.ase import OptSchema
from quacc.schemas._aliases.vasp import QMOFRelaxSchema, VaspSchema
from quacc.utils.files import Filenames, SourceDirectory
LOGGER = logging.getLogger(__name__)
@job
def qmof_relax_job(
atoms: Atoms,
relax_cell: bool = True,
run_prerelax: bool = True,
copy_files: SourceDirectory | dict[SourceDirectory, Filenames] | None = None,
**calc_kwargs,
) -> QMOFRelaxSchema:
"""
Relax a structure in a multi-step process for increased computational efficiency.
This is all done in a single compute job. Settings are such that they are compatible
with the QMOF Database.
1. A "pre-relaxation" with BFGSLineSearch to resolve very high forces.
2. Position relaxation with default ENCUT and coarse k-point grid.
3. Optional: volume relaxation with coarse k-point grid.
4. Double relaxation using production-quality settings.
5. Static calculation.
Parameters
----------
atoms
Atoms object
relax_cell
True if a volume relaxation should be performed. False if only the
positions should be updated.
run_prerelax
If True, a pre-relax will be carried out with BFGSLineSearch.
Recommended if starting from hypothetical structures or materials with
very high starting forces.
copy_files
Files to copy (and decompress) from source to the runtime directory.
**calc_kwargs
Custom kwargs for the calculator. Set a value to `None` to remove
a pre-existing key entirely. Applies for all jobs.
Returns
-------
QMOFRelaxSchema
Dictionary of results. See the type-hint for the data structure.
"""
copy_files = None
# 1. Pre-relaxation
if run_prerelax:
summary1 = _prerelax(atoms, **calc_kwargs)
atoms = summary1["atoms"]
copy_files = {summary1["dir_name"]: ["WAVECAR*"]}
# 2. Position relaxation (loose)
summary2 = _loose_relax_positions(atoms, copy_files=copy_files, **calc_kwargs)
atoms = summary2["atoms"]
copy_files = {summary2["dir_name"]: ["WAVECAR*"]}
# 3. Optional: Volume relaxation (loose)
if relax_cell:
summary3 = _loose_relax_cell(atoms, copy_files=copy_files, **calc_kwargs)
atoms = summary3["atoms"]
copy_files = {summary3["dir_name"]: ["WAVECAR*"]}
# 4. Double Relaxation
# This is done for two reasons: a) because it can
# resolve repadding issues when dV is large; b) because we can use LREAL =
# Auto for the first relaxation and the default LREAL for the second.
summary4 = _double_relax(
atoms, relax_cell=relax_cell, copy_files=copy_files, **calc_kwargs
)
atoms = summary4[-1]["atoms"]
copy_files = {summary4[-1]["dir_name"]: ["WAVECAR*"]}
# 5. Static Calculation
summary5 = _static(atoms, copy_files=copy_files, **calc_kwargs)
summary5["prerelax_lowacc"] = summary1 if run_prerelax else None
summary5["position_relax_lowacc"] = summary2
summary5["volume_relax_lowacc"] = summary3 if relax_cell else None
summary5["double_relax"] = summary4
return summary5
def _prerelax(
atoms: Atoms,
copy_files: SourceDirectory | dict[SourceDirectory, Filenames] | None = None,
**calc_kwargs,
) -> OptSchema:
"""
A "pre-relaxation" with BFGSLineSearch to resolve very high forces.
Parameters
----------
atoms
Atoms object
copy_files
Files to copy (and decompress) from source to the runtime directory.
**calc_kwargs
Custom kwargs for the calculator. Set a value to `None` to remove
a pre-existing key entirely.
Returns
-------
OptSchema
Dictionary of results from [quacc.schemas.ase.summarize_opt_run][].
See the type-hint for the data structure.
"""
calc_defaults = {
"pmg_kpts": {"kppa": 100},
"ediff": 1e-4,
"encut": None,
"lcharg": False,
"lreal": "auto",
"lwave": True,
"nelm": 225,
"nsw": 0,
}
return run_and_summarize_opt(
atoms,
preset="QMOFSet",
calc_defaults=calc_defaults,
calc_swaps=calc_kwargs,
opt_defaults={"fmax": 5.0, "optimizer": BFGSLineSearch},
additional_fields={"name": "QMOF Prerelax"},
copy_files=copy_files,
)
def _loose_relax_positions(
atoms: Atoms,
copy_files: SourceDirectory | dict[SourceDirectory, Filenames] | None = None,
**calc_kwargs,
) -> VaspSchema:
"""
Position relaxation with default ENCUT and coarse k-point grid.
Parameters
----------
atoms
Atoms object
copy_files
Files to copy (and decompress) from source to the runtime directory.
**calc_kwargs
Custom kwargs for the calculator. Set a value to `None` to remove
a pre-existing key entirely.
Returns
-------
VaspSchema
Dictionary of results from [quacc.schemas.vasp.vasp_summarize_run][].
See the type-hint for the data structure.
"""
calc_defaults = {
"pmg_kpts": {"kppa": 100},
"ediff": 1e-4,
"ediffg": -0.05,
"encut": None,
"ibrion": 2,
"isif": 2,
"lcharg": False,
"lreal": "auto",
"lwave": True,
"nsw": 250,
}
return run_and_summarize(
atoms,
preset="QMOFSet",
calc_defaults=calc_defaults,
calc_swaps=calc_kwargs,
additional_fields={"name": "QMOF Loose Relax"},
copy_files=copy_files,
)
def _loose_relax_cell(
atoms: Atoms,
copy_files: SourceDirectory | dict[SourceDirectory, Filenames] | None = None,
**calc_kwargs,
) -> VaspSchema:
"""
Volume relaxation with coarse k-point grid.
Parameters
----------
atoms
Atoms object
copy_files
Files to copy (and decompress) from source to the runtime directory.
**calc_kwargs
Custom kwargs for the calculator. Set a value to `None` to remove
a pre-existing key entirely.
Returns
-------
VaspSchema
Dictionary of results from [quacc.schemas.vasp.vasp_summarize_run][].
See the type-hint for the data structure.
"""
calc_defaults = {
"pmg_kpts": {"kppa": 100},
"ediffg": -0.03,
"ibrion": 2,
"isif": 3,
"lcharg": False,
"lreal": "auto",
"lwave": True,
"nsw": 500,
}
return run_and_summarize(
atoms,
preset="QMOFSet",
calc_defaults=calc_defaults,
calc_swaps=calc_kwargs,
additional_fields={"name": "QMOF Loose Relax Volume"},
copy_files=copy_files,
)
def _double_relax(
atoms: Atoms,
copy_files: SourceDirectory | dict[SourceDirectory, Filenames] | None = None,
relax_cell: bool = True,
**calc_kwargs,
) -> list[VaspSchema]:
"""
Double relaxation using production-quality settings.
Parameters
----------
atoms
Atoms object
relax_cell
True if a volume relaxation should be performed.
copy_files
Files to copy (and decompress) from source to the runtime directory.
**calc_kwargs
Dictionary of custom kwargs for the calculator. Set a value to `None` to remove
a pre-existing key entirely.
Returns
-------
list[VaspSchema]
List of dictionary of results from [quacc.schemas.vasp.vasp_summarize_run][]
See the type-hint for the data structure.
"""
# Run first relaxation
calc_defaults = {
"ediffg": -0.03,
"ibrion": 2,
"isif": 3 if relax_cell else 2,
"lcharg": False,
"lreal": "auto",
"lwave": True,
"nsw": 500 if relax_cell else 250,
}
# To ensure vasp_gam --> vasp_std issues are auto-fixed
with change_settings({"VASP_USE_CUSTODIAN": True}):
summary1 = run_and_summarize(
atoms,
preset="QMOFSet",
calc_defaults=calc_defaults,
calc_swaps=calc_kwargs,
additional_fields={"name": "QMOF DoubleRelax 1"},
copy_files=copy_files,
)
# Update atoms for Relaxation 2
atoms = summary1["atoms"]
# Reset LREAL
del calc_defaults["lreal"]
# Run second relaxation
summary2 = run_and_summarize(
summary1["atoms"],
preset="QMOFSet",
calc_defaults=calc_defaults,
calc_swaps=calc_kwargs,
additional_fields={"name": "QMOF DoubleRelax 2"},
copy_files={summary1["dir_name"]: ["WAVECAR*"]},
)
return [summary1, summary2]
def _static(
atoms: Atoms,
copy_files: SourceDirectory | dict[SourceDirectory, Filenames] | None = None,
**calc_kwargs,
) -> VaspSchema:
"""
Static calculation using production-quality settings.
Parameters
----------
atoms
Atoms object
copy_files
Files to copy (and decompress) from source to the runtime directory.
**calc_kwargs
Custom kwargs for the calculator. Set a value to `None` to remove
a pre-existing key entirely.
Returns
-------
VaspSchema
Dictionary of results from [quacc.schemas.vasp.vasp_summarize_run][].
See the type-hint for the data structure.
"""
calc_defaults = {
"laechg": True,
"lcharg": True,
"lreal": False,
"lwave": True,
"nsw": 0,
}
return run_and_summarize(
atoms,
preset="QMOFSet",
calc_defaults=calc_defaults,
calc_swaps=calc_kwargs,
additional_fields={"name": "QMOF Static"},
copy_files=copy_files,
)