nomad-parser-plugins-workflow 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nomad_parser_plugins_workflow-1.0.dist-info/LICENSE +202 -0
- nomad_parser_plugins_workflow-1.0.dist-info/METADATA +319 -0
- nomad_parser_plugins_workflow-1.0.dist-info/RECORD +58 -0
- nomad_parser_plugins_workflow-1.0.dist-info/WHEEL +5 -0
- nomad_parser_plugins_workflow-1.0.dist-info/entry_points.txt +11 -0
- nomad_parser_plugins_workflow-1.0.dist-info/top_level.txt +1 -0
- workflowparsers/__init__.py +314 -0
- workflowparsers/aflow/__init__.py +19 -0
- workflowparsers/aflow/__main__.py +31 -0
- workflowparsers/aflow/metainfo/__init__.py +19 -0
- workflowparsers/aflow/metainfo/aflow.py +1240 -0
- workflowparsers/aflow/parser.py +741 -0
- workflowparsers/asr/__init__.py +19 -0
- workflowparsers/asr/__main__.py +31 -0
- workflowparsers/asr/metainfo/__init__.py +19 -0
- workflowparsers/asr/metainfo/asr.py +306 -0
- workflowparsers/asr/parser.py +266 -0
- workflowparsers/atomate/__init__.py +19 -0
- workflowparsers/atomate/__main__.py +31 -0
- workflowparsers/atomate/metainfo/__init__.py +19 -0
- workflowparsers/atomate/metainfo/atomate.py +395 -0
- workflowparsers/atomate/parser.py +357 -0
- workflowparsers/elastic/__init__.py +19 -0
- workflowparsers/elastic/__main__.py +31 -0
- workflowparsers/elastic/metainfo/__init__.py +19 -0
- workflowparsers/elastic/metainfo/elastic.py +364 -0
- workflowparsers/elastic/parser.py +798 -0
- workflowparsers/fhivibes/__init__.py +19 -0
- workflowparsers/fhivibes/__main__.py +31 -0
- workflowparsers/fhivibes/metainfo/__init__.py +19 -0
- workflowparsers/fhivibes/metainfo/fhi_vibes.py +898 -0
- workflowparsers/fhivibes/parser.py +566 -0
- workflowparsers/lobster/__init__.py +19 -0
- workflowparsers/lobster/__main__.py +31 -0
- workflowparsers/lobster/metainfo/__init__.py +19 -0
- workflowparsers/lobster/metainfo/lobster.py +446 -0
- workflowparsers/lobster/parser.py +618 -0
- workflowparsers/phonopy/__init__.py +19 -0
- workflowparsers/phonopy/__main__.py +31 -0
- workflowparsers/phonopy/calculator.py +260 -0
- workflowparsers/phonopy/metainfo/__init__.py +19 -0
- workflowparsers/phonopy/metainfo/phonopy.py +83 -0
- workflowparsers/phonopy/parser.py +583 -0
- workflowparsers/quantum_espresso_epw/__init__.py +19 -0
- workflowparsers/quantum_espresso_epw/__main__.py +31 -0
- workflowparsers/quantum_espresso_epw/metainfo/__init__.py +19 -0
- workflowparsers/quantum_espresso_epw/metainfo/quantum_espresso_epw.py +579 -0
- workflowparsers/quantum_espresso_epw/parser.py +583 -0
- workflowparsers/quantum_espresso_phonon/__init__.py +19 -0
- workflowparsers/quantum_espresso_phonon/__main__.py +31 -0
- workflowparsers/quantum_espresso_phonon/metainfo/__init__.py +19 -0
- workflowparsers/quantum_espresso_phonon/metainfo/quantum_espresso_phonon.py +389 -0
- workflowparsers/quantum_espresso_phonon/parser.py +483 -0
- workflowparsers/quantum_espresso_xspectra/__init__.py +19 -0
- workflowparsers/quantum_espresso_xspectra/__main__.py +31 -0
- workflowparsers/quantum_espresso_xspectra/metainfo/__init__.py +19 -0
- workflowparsers/quantum_espresso_xspectra/metainfo/quantum_espresso_xspectra.py +290 -0
- workflowparsers/quantum_espresso_xspectra/parser.py +586 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright The NOMAD Authors.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of NOMAD.
|
|
5
|
+
# See https://nomad-lab.eu for further info.
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
#
|
|
19
|
+
import os
|
|
20
|
+
import numpy as np
|
|
21
|
+
import logging
|
|
22
|
+
import json
|
|
23
|
+
import phonopy
|
|
24
|
+
from phonopy.units import THzToEv
|
|
25
|
+
from phonopy.structure.atoms import PhonopyAtoms
|
|
26
|
+
|
|
27
|
+
from .calculator import PhononProperties
|
|
28
|
+
|
|
29
|
+
from nomad import config # type: ignore
|
|
30
|
+
from nomad.units import ureg
|
|
31
|
+
from nomad.parsing.file_parser import TextParser, Quantity
|
|
32
|
+
from runschema.run import Run, Program
|
|
33
|
+
from runschema.method import Method, Electronic
|
|
34
|
+
from runschema.system import System, Atoms
|
|
35
|
+
from runschema.calculation import (
|
|
36
|
+
Calculation,
|
|
37
|
+
BandStructure,
|
|
38
|
+
BandEnergies,
|
|
39
|
+
Dos,
|
|
40
|
+
DosValues,
|
|
41
|
+
Thermodynamics,
|
|
42
|
+
)
|
|
43
|
+
from simulationworkflowschema import Phonon, PhononMethod, PhononResults
|
|
44
|
+
from nomad.datamodel.metainfo.workflow import Link, Task
|
|
45
|
+
from nomad.datamodel import EntryArchive
|
|
46
|
+
|
|
47
|
+
from .metainfo import phonopy as phonopymetainfo # pylint: disable=unused-import
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def read_aims(filename):
|
|
51
|
+
"""Method to read FHI-aims geometry files in phonopy context."""
|
|
52
|
+
cell = []
|
|
53
|
+
positions = []
|
|
54
|
+
fractional = []
|
|
55
|
+
symbols = []
|
|
56
|
+
magmoms = []
|
|
57
|
+
if not os.path.isfile(filename):
|
|
58
|
+
return
|
|
59
|
+
with open(filename) as f:
|
|
60
|
+
while True:
|
|
61
|
+
line = f.readline()
|
|
62
|
+
if not line:
|
|
63
|
+
break
|
|
64
|
+
line = line.split()
|
|
65
|
+
if len(line) == 0:
|
|
66
|
+
continue
|
|
67
|
+
if line[0] == 'lattice_vector':
|
|
68
|
+
cell.append([float(x) for x in line[1:4]])
|
|
69
|
+
elif line[0].startswith('atom'):
|
|
70
|
+
fractional.append(line[0] == 'atom_frac')
|
|
71
|
+
positions.append([float(x) for x in line[1:4]])
|
|
72
|
+
symbols.append(line[4])
|
|
73
|
+
elif line[0] == 'initial_moment':
|
|
74
|
+
magmoms.append(float(line[1]))
|
|
75
|
+
|
|
76
|
+
for n, pos in enumerate(positions):
|
|
77
|
+
if fractional[n]:
|
|
78
|
+
positions[n] = [
|
|
79
|
+
sum([pos[j] * cell[j][i] for j in range(3)]) for i in range(3)
|
|
80
|
+
]
|
|
81
|
+
if len(magmoms) == len(positions):
|
|
82
|
+
return PhonopyAtoms(
|
|
83
|
+
cell=cell, symbols=symbols, positions=positions, magmoms=magmoms
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
return PhonopyAtoms(cell=cell, symbols=symbols, positions=positions)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Atoms_with_forces(PhonopyAtoms):
|
|
90
|
+
"""Hack to phonopy.atoms to maintain ASE compatibility also for forces."""
|
|
91
|
+
|
|
92
|
+
def get_forces(self):
|
|
93
|
+
return self.forces
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def read_aims_output(filename):
|
|
97
|
+
"""Read FHI-aims output
|
|
98
|
+
returns geometry with forces from last self-consistency iteration"""
|
|
99
|
+
cell = []
|
|
100
|
+
symbols = []
|
|
101
|
+
positions = []
|
|
102
|
+
forces = []
|
|
103
|
+
N = 0
|
|
104
|
+
|
|
105
|
+
with open(filename) as f:
|
|
106
|
+
while True:
|
|
107
|
+
line = f.readline()
|
|
108
|
+
if not line:
|
|
109
|
+
break
|
|
110
|
+
if 'Number of atoms' in line:
|
|
111
|
+
N = int(line.split()[5])
|
|
112
|
+
elif '| Unit cell:' in line:
|
|
113
|
+
cell = [[float(x) for x in f.readline().split()[1:4]] for _ in range(3)]
|
|
114
|
+
elif 'Atomic structure:' in line or 'Updated atomic structure:' in line:
|
|
115
|
+
positions = []
|
|
116
|
+
symbols = []
|
|
117
|
+
symbol_index = 3 if 'Atomic' in line else 4
|
|
118
|
+
position_index = 4 if 'Atomic' in line else 1
|
|
119
|
+
while len(positions) != N:
|
|
120
|
+
line = f.readline()
|
|
121
|
+
if 'Species' in line or 'atom ' in line:
|
|
122
|
+
line = line.split()
|
|
123
|
+
positions.append(
|
|
124
|
+
[
|
|
125
|
+
float(x)
|
|
126
|
+
for x in line[position_index : position_index + 3]
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
symbols.append(line[symbol_index])
|
|
130
|
+
elif 'Total atomic forces' in line:
|
|
131
|
+
forces = [
|
|
132
|
+
[float(x) for x in f.readline().split()[2:5]] for _ in range(N)
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
atoms = Atoms_with_forces(cell=cell, symbols=symbols, positions=positions)
|
|
136
|
+
atoms.forces = forces
|
|
137
|
+
|
|
138
|
+
return atoms
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def read_forces_aims(reference_supercells, tolerance=1e-6, logger=None):
|
|
142
|
+
"""
|
|
143
|
+
Collect the pre calculated forces for each of the supercells
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def get_aims_output_file(directory):
|
|
147
|
+
files = [f for f in os.listdir(directory) if f.endswith('.out')]
|
|
148
|
+
output = None
|
|
149
|
+
for f in files:
|
|
150
|
+
try:
|
|
151
|
+
output = read_aims_output(os.path.join(directory, f))
|
|
152
|
+
break
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
return output, f
|
|
156
|
+
|
|
157
|
+
def is_equal(reference, calculated):
|
|
158
|
+
if len(reference) != len(calculated):
|
|
159
|
+
logger.warning('Inconsistent number of atoms.')
|
|
160
|
+
return False
|
|
161
|
+
if (reference.get_atomic_numbers() != calculated.get_atomic_numbers()).any():
|
|
162
|
+
logger.warning('Inconsistent species.')
|
|
163
|
+
return False
|
|
164
|
+
if (abs(reference.get_cell() - calculated.get_cell()) > tolerance).any():
|
|
165
|
+
logger.warning('Inconsistent cell.')
|
|
166
|
+
return False
|
|
167
|
+
# get normalized positions, wrapped to the bounding cell
|
|
168
|
+
ref_pos = reference.get_scaled_positions() % 1.0
|
|
169
|
+
cal_pos = calculated.get_scaled_positions() % 1.0
|
|
170
|
+
# resolve coordinates at the boundary
|
|
171
|
+
ref_pos = np.where(ref_pos != 1.0, ref_pos, 0.0)
|
|
172
|
+
cal_pos = np.where(cal_pos != 1.0, cal_pos, 0.0)
|
|
173
|
+
if (abs(ref_pos - cal_pos) > tolerance).any():
|
|
174
|
+
logger.warning('Inconsistent positions.')
|
|
175
|
+
return False
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
reference_paths, forces_sets = [], []
|
|
179
|
+
|
|
180
|
+
n_pad = int(np.ceil(np.log10(len(reference_supercells) + 1))) + 1
|
|
181
|
+
for n, reference_supercell in enumerate(reference_supercells):
|
|
182
|
+
directory = 'phonopy-FHI-aims-displacement-%s' % (str(n + 1).zfill(n_pad))
|
|
183
|
+
filename = os.path.join(directory, '%s.out' % directory)
|
|
184
|
+
if os.path.isfile(filename):
|
|
185
|
+
calculated_supercell = read_aims_output(filename)
|
|
186
|
+
else:
|
|
187
|
+
# try reading out files
|
|
188
|
+
calculated_supercell, filename = get_aims_output_file(directory)
|
|
189
|
+
filename = os.path.join(directory, filename)
|
|
190
|
+
|
|
191
|
+
# compare if calculated cell really corresponds to supercell
|
|
192
|
+
if not is_equal(reference_supercell, calculated_supercell):
|
|
193
|
+
logger.error('Supercells do not match')
|
|
194
|
+
|
|
195
|
+
forces = np.array(calculated_supercell.get_forces())
|
|
196
|
+
drift_force = forces.sum(axis=0)
|
|
197
|
+
for force in forces:
|
|
198
|
+
force -= drift_force / forces.shape[0]
|
|
199
|
+
forces_sets.append(forces)
|
|
200
|
+
reference_paths.append(filename)
|
|
201
|
+
return forces_sets, reference_paths
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class ControlParser(TextParser):
|
|
205
|
+
def __init__(self):
|
|
206
|
+
super().__init__()
|
|
207
|
+
|
|
208
|
+
def init_quantities(self):
|
|
209
|
+
def str_to_nac(val_in):
|
|
210
|
+
val = val_in.strip().split()
|
|
211
|
+
nac = dict(file=val[0], method=val[1].lower())
|
|
212
|
+
if len(val) > 2:
|
|
213
|
+
nac['delta'] = [float(v) for v in val[3:6]]
|
|
214
|
+
return nac
|
|
215
|
+
|
|
216
|
+
def str_to_supercell(val_in):
|
|
217
|
+
val = [int(v) for v in val_in.strip().split()]
|
|
218
|
+
if len(val) == 3:
|
|
219
|
+
return np.diag(val)
|
|
220
|
+
else:
|
|
221
|
+
return np.reshape(val, (3, 3))
|
|
222
|
+
|
|
223
|
+
self._quantities = [
|
|
224
|
+
Quantity(
|
|
225
|
+
'displacement', r'\n *phonon displacement\s*([\d\.]+)', dtype=float
|
|
226
|
+
),
|
|
227
|
+
Quantity(
|
|
228
|
+
'symmetry_thresh',
|
|
229
|
+
r'\n *phonon symmetry_thresh\s*([\d\.]+)',
|
|
230
|
+
dtype=float,
|
|
231
|
+
),
|
|
232
|
+
Quantity('frequency_unit', r'\n *phonon frequency_unit\s*(\S+)'),
|
|
233
|
+
Quantity(
|
|
234
|
+
'supercell',
|
|
235
|
+
r'\n *phonon supercell\s*(.+)',
|
|
236
|
+
str_operation=str_to_supercell,
|
|
237
|
+
),
|
|
238
|
+
Quantity('nac', r'\n *phonon nac\s*(.+)', str_operation=str_to_nac),
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def phonopy_obj_to_archive(
|
|
243
|
+
phonopy_obj, references=[], archive=None, filename=None, logger=None, **kwargs
|
|
244
|
+
):
|
|
245
|
+
"""
|
|
246
|
+
Executes Phonopy starting from a phonopy object and write the results on a nomad archive.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def parse_bandstructure():
|
|
250
|
+
freqs, bands, bands_labels = properties.get_bandstructure()
|
|
251
|
+
if freqs is None:
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
# convert THz to eV
|
|
255
|
+
freqs = freqs * THzToEv
|
|
256
|
+
|
|
257
|
+
# convert eV to J
|
|
258
|
+
freqs = (freqs * ureg.eV).to('joules').magnitude
|
|
259
|
+
|
|
260
|
+
sec_scc = archive.run[0].calculation[0]
|
|
261
|
+
|
|
262
|
+
sec_k_band = BandStructure()
|
|
263
|
+
sec_scc.band_structure_phonon.append(sec_k_band)
|
|
264
|
+
|
|
265
|
+
for i in range(len(freqs)):
|
|
266
|
+
sec_k_band_segment = BandEnergies()
|
|
267
|
+
sec_k_band.segment.append(sec_k_band_segment)
|
|
268
|
+
sec_k_band_segment.kpoints = bands[i]
|
|
269
|
+
sec_k_band_segment.endpoints_labels = [
|
|
270
|
+
str(label) for label in bands_labels[i]
|
|
271
|
+
]
|
|
272
|
+
sec_k_band_segment.energies = [freqs[i]]
|
|
273
|
+
|
|
274
|
+
def parse_dos():
|
|
275
|
+
f, dos = properties.get_dos()
|
|
276
|
+
|
|
277
|
+
# convert THz to eV to Joules
|
|
278
|
+
f = f * THzToEv
|
|
279
|
+
f = (f * ureg.eV).to('joules').magnitude
|
|
280
|
+
|
|
281
|
+
sec_scc = archive.run[0].calculation[0]
|
|
282
|
+
sec_dos = Dos()
|
|
283
|
+
sec_scc.dos_phonon.append(sec_dos)
|
|
284
|
+
sec_dos.energies = f
|
|
285
|
+
sec_dos_values = DosValues()
|
|
286
|
+
sec_dos.total.append(sec_dos_values)
|
|
287
|
+
sec_dos_values.value = dos
|
|
288
|
+
|
|
289
|
+
def parse_thermodynamical_properties():
|
|
290
|
+
T, fe, _, cv = properties.get_thermodynamical_properties()
|
|
291
|
+
|
|
292
|
+
n_atoms = len(phonopy_obj.unitcell)
|
|
293
|
+
n_atoms_supercell = len(phonopy_obj.supercell)
|
|
294
|
+
|
|
295
|
+
fe = fe / n_atoms
|
|
296
|
+
|
|
297
|
+
# The thermodynamic properties are reported by phonopy for the base
|
|
298
|
+
# system. Since the values in the metainfo are stored per the referenced
|
|
299
|
+
# system, we need to multiple by the size factor between the base system
|
|
300
|
+
# and the supersystem used in the calculations.
|
|
301
|
+
cv = cv * (n_atoms_supercell / n_atoms)
|
|
302
|
+
|
|
303
|
+
# convert to SI units
|
|
304
|
+
fe = (fe * ureg.eV).to('joules').magnitude
|
|
305
|
+
|
|
306
|
+
cv = (cv * ureg.eV / ureg.K).to('joules/K').magnitude
|
|
307
|
+
|
|
308
|
+
sec_run = archive.run[0]
|
|
309
|
+
sec_scc = sec_run.calculation[0]
|
|
310
|
+
|
|
311
|
+
for n, Tn in enumerate(T):
|
|
312
|
+
sec_thermo_prop = Thermodynamics()
|
|
313
|
+
sec_scc.thermodynamics.append(sec_thermo_prop)
|
|
314
|
+
sec_thermo_prop.temperature = Tn
|
|
315
|
+
sec_thermo_prop.vibrational_free_energy_at_constant_volume = fe[n]
|
|
316
|
+
sec_thermo_prop.heat_capacity_c_v = cv[n]
|
|
317
|
+
|
|
318
|
+
# TODO create a taylor_expansion workflow?
|
|
319
|
+
# sampling_method = 'taylor_expansion'
|
|
320
|
+
# expansion_order = 2
|
|
321
|
+
|
|
322
|
+
logger = logger if logger is not None else logging
|
|
323
|
+
archive = archive if archive else EntryArchive()
|
|
324
|
+
|
|
325
|
+
pbc = np.array((1, 1, 1), bool)
|
|
326
|
+
|
|
327
|
+
unit_cell = phonopy_obj.unitcell.get_cell()
|
|
328
|
+
unit_pos = phonopy_obj.unitcell.get_positions()
|
|
329
|
+
unit_sym = np.array(phonopy_obj.unitcell.get_chemical_symbols())
|
|
330
|
+
|
|
331
|
+
super_cell = phonopy_obj.supercell.get_cell()
|
|
332
|
+
super_pos = phonopy_obj.supercell.get_positions()
|
|
333
|
+
super_sym = np.array(phonopy_obj.supercell.get_chemical_symbols())
|
|
334
|
+
|
|
335
|
+
unit_cell = (unit_cell * ureg.angstrom).to('meter').magnitude
|
|
336
|
+
unit_pos = (unit_pos * ureg.angstrom).to('meter').magnitude
|
|
337
|
+
|
|
338
|
+
super_cell = (super_cell * ureg.angstrom).to('meter').magnitude
|
|
339
|
+
super_pos = (super_pos * ureg.angstrom).to('meter').magnitude
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
displacement = np.linalg.norm(phonopy_obj.displacements[0][1:])
|
|
343
|
+
displacement = displacement * ureg.angstrom
|
|
344
|
+
except Exception:
|
|
345
|
+
displacement = None
|
|
346
|
+
|
|
347
|
+
supercell_matrix = phonopy_obj.supercell_matrix
|
|
348
|
+
sym_tol = phonopy_obj.symmetry.tolerance
|
|
349
|
+
|
|
350
|
+
sec_run = Run()
|
|
351
|
+
archive.run.append(sec_run)
|
|
352
|
+
sec_run.program = Program(name='Phonopy', version=phonopy.__version__)
|
|
353
|
+
|
|
354
|
+
sec_system_unit = System()
|
|
355
|
+
sec_run.system.append(sec_system_unit)
|
|
356
|
+
sec_atoms = Atoms()
|
|
357
|
+
sec_system_unit.atoms = sec_atoms
|
|
358
|
+
sec_atoms.periodic = pbc
|
|
359
|
+
sec_atoms.labels = unit_sym
|
|
360
|
+
sec_atoms.positions = unit_pos
|
|
361
|
+
sec_atoms.lattice_vectors = unit_cell
|
|
362
|
+
|
|
363
|
+
sec_system = System()
|
|
364
|
+
sec_run.system.append(sec_system)
|
|
365
|
+
sec_system.sub_system_ref = sec_system_unit
|
|
366
|
+
sec_system.systems_ref = [sec_system_unit]
|
|
367
|
+
sec_atoms = Atoms()
|
|
368
|
+
sec_system.atoms = sec_atoms
|
|
369
|
+
sec_atoms.periodic = pbc
|
|
370
|
+
sec_atoms.labels = super_sym
|
|
371
|
+
sec_atoms.positions = super_pos
|
|
372
|
+
sec_atoms.lattice_vectors = super_cell
|
|
373
|
+
sec_atoms.supercell_matrix = supercell_matrix
|
|
374
|
+
sec_system.x_phonopy_original_system_ref = sec_system_unit
|
|
375
|
+
|
|
376
|
+
sec_method = Method()
|
|
377
|
+
sec_run.method.append(sec_method)
|
|
378
|
+
# TODO I put this so as to have a recognizable section method, but metainfo
|
|
379
|
+
# should be expanded to include phonon related method parameters
|
|
380
|
+
sec_method.electronic = Electronic(method='DFT')
|
|
381
|
+
sec_method.x_phonopy_symprec = sym_tol
|
|
382
|
+
if displacement is not None:
|
|
383
|
+
sec_method.x_phonopy_displacement = displacement
|
|
384
|
+
|
|
385
|
+
try:
|
|
386
|
+
force_constants = phonopy_obj.get_force_constants()
|
|
387
|
+
force_constants = (
|
|
388
|
+
(force_constants * ureg.eV / ureg.angstrom**2).to('J/(m**2)').magnitude
|
|
389
|
+
)
|
|
390
|
+
except Exception:
|
|
391
|
+
logger.error('Error producing force constants.')
|
|
392
|
+
return
|
|
393
|
+
|
|
394
|
+
sec_scc = Calculation()
|
|
395
|
+
sec_run.calculation.append(sec_scc)
|
|
396
|
+
sec_scc.system_ref = sec_system
|
|
397
|
+
sec_scc.method_ref = sec_method
|
|
398
|
+
sec_scc.hessian_matrix = force_constants
|
|
399
|
+
|
|
400
|
+
# run Phonopy
|
|
401
|
+
properties = PhononProperties(phonopy_obj, logger, **kwargs)
|
|
402
|
+
|
|
403
|
+
parse_bandstructure()
|
|
404
|
+
parse_dos()
|
|
405
|
+
parse_thermodynamical_properties()
|
|
406
|
+
|
|
407
|
+
# create workflow section
|
|
408
|
+
vol = np.dot(unit_cell[0], np.cross(unit_cell[1], unit_cell[2]))
|
|
409
|
+
n_imaginary = np.count_nonzero(properties.frequencies < 0)
|
|
410
|
+
|
|
411
|
+
workflow = Phonon(method=PhononMethod(), results=PhononResults())
|
|
412
|
+
workflow.method.force_calculator = phonopy_obj.calculator
|
|
413
|
+
workflow.method.mesh_density = np.prod(properties.mesh) / vol
|
|
414
|
+
workflow.results.n_imaginary_frequencies = n_imaginary
|
|
415
|
+
if phonopy_obj.nac_params:
|
|
416
|
+
workflow.method.with_non_analytic_correction = True
|
|
417
|
+
inputs = []
|
|
418
|
+
for path in references:
|
|
419
|
+
try:
|
|
420
|
+
archive = archive.m_context.resolve_archive(
|
|
421
|
+
f'../upload/archive/mainfile/{path}'
|
|
422
|
+
)
|
|
423
|
+
section = archive.run[0].calculation[0]
|
|
424
|
+
except Exception as e:
|
|
425
|
+
section = None
|
|
426
|
+
logger.error(
|
|
427
|
+
'Could not resolve referenced calculations.', exc_info=e, path=path
|
|
428
|
+
)
|
|
429
|
+
if section is not None:
|
|
430
|
+
inputs.append(Link(name='Input calculation', section=section))
|
|
431
|
+
workflow.inputs = inputs
|
|
432
|
+
workflow.outputs = [Link(name='Phonon results', section=f'/workflow2/results')]
|
|
433
|
+
workflow.tasks = [
|
|
434
|
+
Task(
|
|
435
|
+
name='Phonon calculation', inputs=workflow.inputs, outputs=workflow.outputs
|
|
436
|
+
)
|
|
437
|
+
]
|
|
438
|
+
archive.workflow2 = workflow
|
|
439
|
+
|
|
440
|
+
if filename:
|
|
441
|
+
with open(filename, 'w') as f:
|
|
442
|
+
json.dump(archive.m_to_dict(), f, indent=4)
|
|
443
|
+
|
|
444
|
+
return archive
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class PhonopyParser:
|
|
448
|
+
level = 1
|
|
449
|
+
|
|
450
|
+
def __init__(self, **kwargs):
|
|
451
|
+
# super().__init__(
|
|
452
|
+
# name='parsers/phonopy', code_name='Phonopy', code_homepage='https://phonopy.github.io/phonopy/',
|
|
453
|
+
# mainfile_name_re=(r'(.*/phonopy-FHI-aims-displacement-0*1/control.in$)|(.*/phon.+yaml)')
|
|
454
|
+
# )
|
|
455
|
+
self._kwargs = kwargs
|
|
456
|
+
self.control_parser = ControlParser()
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def mainfile(self):
|
|
460
|
+
return self._filepath
|
|
461
|
+
|
|
462
|
+
@mainfile.setter
|
|
463
|
+
def mainfile(self, val):
|
|
464
|
+
self._phonopy_obj = None
|
|
465
|
+
self.references = []
|
|
466
|
+
self._filepath = os.path.abspath(val)
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def phonopy_obj(self):
|
|
470
|
+
if self._phonopy_obj is None:
|
|
471
|
+
if 'control.in' in self.mainfile:
|
|
472
|
+
self._build_phonopy_object_fhi_aims()
|
|
473
|
+
elif self.mainfile.endswith('.yaml'):
|
|
474
|
+
self._build_phonopy_object_yaml()
|
|
475
|
+
return self._phonopy_obj
|
|
476
|
+
|
|
477
|
+
def _build_phonopy_object_yaml(self):
|
|
478
|
+
cwd = os.getcwd()
|
|
479
|
+
os.chdir(os.path.dirname(self.mainfile))
|
|
480
|
+
|
|
481
|
+
try:
|
|
482
|
+
phonopy_obj = phonopy.load(self.mainfile)
|
|
483
|
+
except Exception:
|
|
484
|
+
self.logger.error('Error loading phonopy file.')
|
|
485
|
+
phonopy_obj = None
|
|
486
|
+
finally:
|
|
487
|
+
os.chdir(cwd)
|
|
488
|
+
|
|
489
|
+
self._phonopy_obj = phonopy_obj
|
|
490
|
+
|
|
491
|
+
def _build_phonopy_object_fhi_aims(self):
|
|
492
|
+
cwd = os.getcwd()
|
|
493
|
+
os.chdir(os.path.dirname(os.path.dirname(self.mainfile)))
|
|
494
|
+
try:
|
|
495
|
+
cell_obj = read_aims('geometry.in')
|
|
496
|
+
self.control_parser.mainfile = 'control.in'
|
|
497
|
+
supercell_matrix = self.control_parser.get('supercell')
|
|
498
|
+
displacement = self.control_parser.get('displacement', 0.001)
|
|
499
|
+
sym = self.control_parser.get('symmetry_thresh', 1e-6)
|
|
500
|
+
try:
|
|
501
|
+
phonopy_obj = phonopy.Phonopy(
|
|
502
|
+
cell_obj, supercell_matrix, symprec=sym, calculator='fhi-aims'
|
|
503
|
+
)
|
|
504
|
+
phonopy_obj.generate_displacements(distance=displacement)
|
|
505
|
+
supercells = phonopy_obj.get_supercells_with_displacements()
|
|
506
|
+
set_of_forces, relative_paths = read_forces_aims(
|
|
507
|
+
supercells, logger=self.logger
|
|
508
|
+
)
|
|
509
|
+
except Exception:
|
|
510
|
+
self.logger.error('Error generating phonopy object.')
|
|
511
|
+
set_of_forces = []
|
|
512
|
+
phonopy_obj = None
|
|
513
|
+
relative_paths = []
|
|
514
|
+
|
|
515
|
+
prep_path = self.mainfile.split('phonopy-FHI-aims-displacement-')
|
|
516
|
+
# Try to resolve references as paths relative to the upload root.
|
|
517
|
+
try:
|
|
518
|
+
for path in relative_paths:
|
|
519
|
+
abs_path = '%s%s' % (prep_path[0], path)
|
|
520
|
+
rel_path = abs_path.split(config.fs.staging + '/')[1].split('/', 3)[
|
|
521
|
+
3
|
|
522
|
+
]
|
|
523
|
+
self.references.append(rel_path)
|
|
524
|
+
except Exception:
|
|
525
|
+
self.logger.warning(
|
|
526
|
+
'Could not resolve path to a referenced calculation within the upload.'
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
finally:
|
|
530
|
+
os.chdir(cwd)
|
|
531
|
+
|
|
532
|
+
if set_of_forces:
|
|
533
|
+
try:
|
|
534
|
+
phonopy_obj.set_forces(set_of_forces)
|
|
535
|
+
phonopy_obj.produce_force_constants()
|
|
536
|
+
except Exception:
|
|
537
|
+
self.logger.error('Error producing force constants.')
|
|
538
|
+
pass
|
|
539
|
+
|
|
540
|
+
self._phonopy_obj = phonopy_obj
|
|
541
|
+
|
|
542
|
+
def parse(self, filepath, archive, logger, **kwargs):
|
|
543
|
+
self.mainfile = os.path.abspath(filepath)
|
|
544
|
+
self.archive = archive
|
|
545
|
+
self.logger = logger if logger is not None else logging
|
|
546
|
+
self._kwargs.update(kwargs)
|
|
547
|
+
|
|
548
|
+
# get bandstructure configuration file
|
|
549
|
+
maindir = os.path.dirname(self.mainfile)
|
|
550
|
+
# TODO read band configuration from phonopy.yaml
|
|
551
|
+
files = [f for f in os.listdir(maindir) if f.endswith('.conf')]
|
|
552
|
+
self._kwargs.update(
|
|
553
|
+
{'band_conf': os.path.join(maindir, files[0]) if files else None}
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
phonopy_obj = self.phonopy_obj
|
|
557
|
+
if phonopy_obj is None:
|
|
558
|
+
self.logger.error('Error running phonopy.')
|
|
559
|
+
return
|
|
560
|
+
|
|
561
|
+
phonopy_obj_to_archive(
|
|
562
|
+
phonopy_obj, references=self.references, archive=archive, logger=logger
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
def after_normalization(self, archive, logger=None) -> None:
|
|
566
|
+
# Overwrite the result method with method details taken from the first referenced
|
|
567
|
+
# calculation. The program name and version are kept.
|
|
568
|
+
self.logger = logger if logger is not None else logging
|
|
569
|
+
try:
|
|
570
|
+
first_referenced_calculation = archive.workflow2.results.calculations_ref[0]
|
|
571
|
+
referenced_archive = first_referenced_calculation.m_root()
|
|
572
|
+
except Exception:
|
|
573
|
+
self.logger.warn('Error getting referenced calculation.')
|
|
574
|
+
return
|
|
575
|
+
|
|
576
|
+
new_method = referenced_archive.results.method.m_copy()
|
|
577
|
+
new_method.simulation.program_name = (
|
|
578
|
+
self.archive.results.method.simulation.program_name
|
|
579
|
+
)
|
|
580
|
+
new_method.simulation.program_version = (
|
|
581
|
+
self.archive.results.method.simulation.program_version
|
|
582
|
+
)
|
|
583
|
+
archive.results.method = new_method
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright The NOMAD Authors.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of NOMAD.
|
|
5
|
+
# See https://nomad-lab.eu for further info.
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
#
|
|
19
|
+
from .parser import QuantumEspressoEPWParser
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright The NOMAD Authors.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of NOMAD.
|
|
5
|
+
# See https://nomad-lab.eu for further info.
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
#
|
|
19
|
+
import sys
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
|
|
23
|
+
from nomad.utils import configure_logging
|
|
24
|
+
from nomad.datamodel import EntryArchive
|
|
25
|
+
from workflowparsers.quantum_espresso_epw import QuantumEspressoEPWParser
|
|
26
|
+
|
|
27
|
+
if __name__ == '__main__':
|
|
28
|
+
configure_logging(console_log_level=logging.DEBUG)
|
|
29
|
+
archive = EntryArchive()
|
|
30
|
+
QuantumEspressoEPWParser().parse(sys.argv[1], archive, logging)
|
|
31
|
+
json.dump(archive.m_to_dict(), sys.stdout, indent=2)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright The NOMAD Authors.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of NOMAD.
|
|
5
|
+
# See https://nomad-lab.eu for further info.
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
#
|
|
19
|
+
from . import quantum_espresso_epw
|