nomad-schema-plugin-simulation-workflow 1.0.1__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_schema_plugin_simulation_workflow-1.0.1.dist-info/LICENSE +202 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/METADATA +17 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/RECORD +22 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/WHEEL +5 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/entry_points.txt +2 -0
- nomad_schema_plugin_simulation_workflow-1.0.1.dist-info/top_level.txt +1 -0
- simulationworkflowschema/__init__.py +118 -0
- simulationworkflowschema/chemical_reaction.py +167 -0
- simulationworkflowschema/dmft.py +84 -0
- simulationworkflowschema/elastic.py +494 -0
- simulationworkflowschema/equation_of_state.py +213 -0
- simulationworkflowschema/general.py +505 -0
- simulationworkflowschema/geometry_optimization.py +349 -0
- simulationworkflowschema/gw.py +69 -0
- simulationworkflowschema/max_ent.py +80 -0
- simulationworkflowschema/molecular_dynamics.py +2601 -0
- simulationworkflowschema/phonon.py +196 -0
- simulationworkflowschema/photon_polarization.py +75 -0
- simulationworkflowschema/single_point.py +273 -0
- simulationworkflowschema/tb.py +77 -0
- simulationworkflowschema/thermodynamics.py +375 -0
- simulationworkflowschema/xs.py +100 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright The NOMAD Authors.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of NOMAD. See https://nomad-lab.eu for further info.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
from nomad.datamodel.data import ArchiveSection
|
|
21
|
+
from nomad.metainfo import SubSection, Section, Quantity
|
|
22
|
+
from nomad.datamodel.metainfo.workflow import Link
|
|
23
|
+
from .general import (
|
|
24
|
+
SimulationWorkflowMethod,
|
|
25
|
+
SimulationWorkflowResults,
|
|
26
|
+
ParallelSimulation,
|
|
27
|
+
WORKFLOW_METHOD_NAME,
|
|
28
|
+
WORKFLOW_RESULTS_NAME,
|
|
29
|
+
)
|
|
30
|
+
from .thermodynamics import ThermodynamicsResults
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class StrainDiagrams(ArchiveSection):
|
|
34
|
+
"""
|
|
35
|
+
Section containing the information regarding the elastic strains.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
m_def = Section(validate=False)
|
|
39
|
+
|
|
40
|
+
type = Quantity(
|
|
41
|
+
type=str,
|
|
42
|
+
shape=[],
|
|
43
|
+
description="""
|
|
44
|
+
Kind of strain diagram. Possible values are: energy; cross-validation (cross-
|
|
45
|
+
validation error); d2E (second derivative of the energy wrt the strain)
|
|
46
|
+
""",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
n_eta = Quantity(
|
|
50
|
+
type=np.int32,
|
|
51
|
+
shape=[],
|
|
52
|
+
description="""
|
|
53
|
+
Number of strain values used in the strain diagram
|
|
54
|
+
""",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
n_deformations = Quantity(
|
|
58
|
+
type=np.int32,
|
|
59
|
+
shape=[],
|
|
60
|
+
description="""
|
|
61
|
+
Number of deformations.
|
|
62
|
+
""",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
value = Quantity(
|
|
66
|
+
type=np.float64,
|
|
67
|
+
shape=['n_deformations', 'n_eta'],
|
|
68
|
+
description="""
|
|
69
|
+
Values of the energy(units:J)/d2E(units:Pa)/cross-validation (depending on the
|
|
70
|
+
value of strain_diagram_type)
|
|
71
|
+
""",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
eta = Quantity(
|
|
75
|
+
type=np.float64,
|
|
76
|
+
shape=['n_deformations', 'n_eta'],
|
|
77
|
+
description="""
|
|
78
|
+
eta values used the strain diagrams
|
|
79
|
+
""",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
stress_voigt_component = Quantity(
|
|
83
|
+
type=np.int32,
|
|
84
|
+
shape=[],
|
|
85
|
+
description="""
|
|
86
|
+
Voigt component corresponding to the strain diagram
|
|
87
|
+
""",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
polynomial_fit_order = Quantity(
|
|
91
|
+
type=np.int32,
|
|
92
|
+
shape=[],
|
|
93
|
+
description="""
|
|
94
|
+
Order of the polynomial fit
|
|
95
|
+
""",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ElasticMethod(SimulationWorkflowMethod):
|
|
100
|
+
energy_stress_calculator = Quantity(
|
|
101
|
+
type=str,
|
|
102
|
+
shape=[],
|
|
103
|
+
description="""
|
|
104
|
+
Name of program used to calculate energy or stress.
|
|
105
|
+
""",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
calculation_method = Quantity(
|
|
109
|
+
type=str,
|
|
110
|
+
shape=[],
|
|
111
|
+
description="""
|
|
112
|
+
Method used to calculate elastic constants, can either be energy or stress.
|
|
113
|
+
""",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
elastic_constants_order = Quantity(
|
|
117
|
+
type=int,
|
|
118
|
+
shape=[],
|
|
119
|
+
description="""
|
|
120
|
+
Order of the calculated elastic constants.
|
|
121
|
+
""",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
fitting_error_maximum = Quantity(
|
|
125
|
+
type=np.float64,
|
|
126
|
+
shape=[],
|
|
127
|
+
description="""
|
|
128
|
+
Maximum error in polynomial fit.
|
|
129
|
+
""",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
strain_maximum = Quantity(
|
|
133
|
+
type=np.float64,
|
|
134
|
+
shape=[],
|
|
135
|
+
description="""
|
|
136
|
+
Maximum strain applied to crystal.
|
|
137
|
+
""",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class ElasticResults(ThermodynamicsResults):
|
|
142
|
+
n_deformations = Quantity(
|
|
143
|
+
type=np.int32,
|
|
144
|
+
shape=[],
|
|
145
|
+
description="""
|
|
146
|
+
Number of deformed structures used to calculate the elastic constants. This is
|
|
147
|
+
determined by the symmetry of the crystal.
|
|
148
|
+
""",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
deformation_types = Quantity(
|
|
152
|
+
type=np.str_,
|
|
153
|
+
shape=['n_deformations', 6],
|
|
154
|
+
description="""
|
|
155
|
+
deformation types
|
|
156
|
+
""",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
n_strains = Quantity(
|
|
160
|
+
type=np.int32,
|
|
161
|
+
shape=[],
|
|
162
|
+
description="""
|
|
163
|
+
number of equally spaced strains applied to each deformed structure, which are
|
|
164
|
+
generated between the maximum negative strain and the maximum positive one.
|
|
165
|
+
""",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
is_mechanically_stable = Quantity(
|
|
169
|
+
type=bool,
|
|
170
|
+
shape=[],
|
|
171
|
+
description="""
|
|
172
|
+
Indicates if structure is mechanically stable from the calculated values of the
|
|
173
|
+
elastic constants.
|
|
174
|
+
""",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
elastic_constants_notation_matrix_second_order = Quantity(
|
|
178
|
+
type=np.str_,
|
|
179
|
+
shape=[6, 6],
|
|
180
|
+
description="""
|
|
181
|
+
Symmetry of the second-order elastic constant matrix in Voigt notation
|
|
182
|
+
""",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
elastic_constants_matrix_second_order = Quantity(
|
|
186
|
+
type=np.float64,
|
|
187
|
+
shape=[6, 6],
|
|
188
|
+
unit='pascal',
|
|
189
|
+
description="""
|
|
190
|
+
2nd order elastic constant (stiffness) matrix in pascals
|
|
191
|
+
""",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
elastic_constants_matrix_third_order = Quantity(
|
|
195
|
+
type=np.float64,
|
|
196
|
+
shape=[6, 6, 6],
|
|
197
|
+
unit='pascal',
|
|
198
|
+
description="""
|
|
199
|
+
3rd order elastic constant (stiffness) matrix in pascals
|
|
200
|
+
""",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
compliance_matrix_second_order = Quantity(
|
|
204
|
+
type=np.float64,
|
|
205
|
+
shape=[6, 6],
|
|
206
|
+
unit='1 / pascal',
|
|
207
|
+
description="""
|
|
208
|
+
Elastic compliance matrix in 1/GPa
|
|
209
|
+
""",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
elastic_constants_gradient_matrix_second_order = Quantity(
|
|
213
|
+
type=np.float64,
|
|
214
|
+
shape=[18, 18],
|
|
215
|
+
unit='newton',
|
|
216
|
+
description="""
|
|
217
|
+
gradient of the 2nd order elastic constant (stiffness) matrix in newton
|
|
218
|
+
""",
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
bulk_modulus_voigt = Quantity(
|
|
222
|
+
type=np.float64,
|
|
223
|
+
shape=[],
|
|
224
|
+
unit='pascal',
|
|
225
|
+
description="""
|
|
226
|
+
Voigt bulk modulus
|
|
227
|
+
""",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
shear_modulus_voigt = Quantity(
|
|
231
|
+
type=np.float64,
|
|
232
|
+
shape=[],
|
|
233
|
+
unit='pascal',
|
|
234
|
+
description="""
|
|
235
|
+
Voigt shear modulus
|
|
236
|
+
""",
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
bulk_modulus_reuss = Quantity(
|
|
240
|
+
type=np.float64,
|
|
241
|
+
shape=[],
|
|
242
|
+
unit='pascal',
|
|
243
|
+
description="""
|
|
244
|
+
Reuss bulk modulus
|
|
245
|
+
""",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
shear_modulus_reuss = Quantity(
|
|
249
|
+
type=np.float64,
|
|
250
|
+
shape=[],
|
|
251
|
+
unit='pascal',
|
|
252
|
+
description="""
|
|
253
|
+
Reuss shear modulus
|
|
254
|
+
""",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
bulk_modulus_hill = Quantity(
|
|
258
|
+
type=np.float64,
|
|
259
|
+
shape=[],
|
|
260
|
+
unit='pascal',
|
|
261
|
+
description="""
|
|
262
|
+
Hill bulk modulus
|
|
263
|
+
""",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
shear_modulus_hill = Quantity(
|
|
267
|
+
type=np.float64,
|
|
268
|
+
shape=[],
|
|
269
|
+
unit='pascal',
|
|
270
|
+
description="""
|
|
271
|
+
Hill shear modulus
|
|
272
|
+
""",
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
young_modulus_voigt = Quantity(
|
|
276
|
+
type=np.float64,
|
|
277
|
+
shape=[],
|
|
278
|
+
unit='pascal',
|
|
279
|
+
description="""
|
|
280
|
+
Voigt Young modulus
|
|
281
|
+
""",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
poisson_ratio_voigt = Quantity(
|
|
285
|
+
type=np.float64,
|
|
286
|
+
shape=[],
|
|
287
|
+
description="""
|
|
288
|
+
Voigt Poisson ratio
|
|
289
|
+
""",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
young_modulus_reuss = Quantity(
|
|
293
|
+
type=np.float64,
|
|
294
|
+
shape=[],
|
|
295
|
+
unit='pascal',
|
|
296
|
+
description="""
|
|
297
|
+
Reuss Young modulus
|
|
298
|
+
""",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
poisson_ratio_reuss = Quantity(
|
|
302
|
+
type=np.float64,
|
|
303
|
+
shape=[],
|
|
304
|
+
description="""
|
|
305
|
+
Reuss Poisson ratio
|
|
306
|
+
""",
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
young_modulus_hill = Quantity(
|
|
310
|
+
type=np.float64,
|
|
311
|
+
shape=[],
|
|
312
|
+
unit='pascal',
|
|
313
|
+
description="""
|
|
314
|
+
Hill Young modulus
|
|
315
|
+
""",
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
poisson_ratio_hill = Quantity(
|
|
319
|
+
type=np.float64,
|
|
320
|
+
shape=[],
|
|
321
|
+
description="""
|
|
322
|
+
Hill Poisson ratio
|
|
323
|
+
""",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
elastic_anisotropy = Quantity(
|
|
327
|
+
type=np.float64,
|
|
328
|
+
shape=[],
|
|
329
|
+
description="""
|
|
330
|
+
Elastic anisotropy
|
|
331
|
+
""",
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
pugh_ratio_hill = Quantity(
|
|
335
|
+
type=np.float64,
|
|
336
|
+
shape=[],
|
|
337
|
+
description="""
|
|
338
|
+
Pugh ratio defined as the ratio between the shear modulus and bulk modulus
|
|
339
|
+
""",
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
debye_temperature = Quantity(
|
|
343
|
+
type=np.float64,
|
|
344
|
+
shape=[],
|
|
345
|
+
unit='kelvin',
|
|
346
|
+
description="""
|
|
347
|
+
Debye temperature
|
|
348
|
+
""",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
speed_sound_transverse = Quantity(
|
|
352
|
+
type=np.float64,
|
|
353
|
+
shape=[],
|
|
354
|
+
unit='meter / second',
|
|
355
|
+
description="""
|
|
356
|
+
Speed of sound along the transverse direction
|
|
357
|
+
""",
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
speed_sound_longitudinal = Quantity(
|
|
361
|
+
type=np.float64,
|
|
362
|
+
shape=[],
|
|
363
|
+
unit='meter / second',
|
|
364
|
+
description="""
|
|
365
|
+
Speed of sound along the longitudinal direction
|
|
366
|
+
""",
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
speed_sound_average = Quantity(
|
|
370
|
+
type=np.float64,
|
|
371
|
+
shape=[],
|
|
372
|
+
unit='meter / second',
|
|
373
|
+
description="""
|
|
374
|
+
Average speed of sound
|
|
375
|
+
""",
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
eigenvalues_elastic = Quantity(
|
|
379
|
+
type=np.float64,
|
|
380
|
+
shape=[6],
|
|
381
|
+
unit='pascal',
|
|
382
|
+
description="""
|
|
383
|
+
Eigenvalues of the stiffness matrix
|
|
384
|
+
""",
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
strain_diagrams = SubSection(sub_section=StrainDiagrams.m_def, repeats=True)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class Elastic(ParallelSimulation):
|
|
391
|
+
method = SubSection(sub_section=ElasticMethod)
|
|
392
|
+
|
|
393
|
+
results = SubSection(sub_section=ElasticResults)
|
|
394
|
+
|
|
395
|
+
def _resolve_mechanical_stability(self):
|
|
396
|
+
spacegroup, c = None, None
|
|
397
|
+
try:
|
|
398
|
+
spacegroup = self._systems[-1].symmetry[-1].space_group_number
|
|
399
|
+
c = self.results.elastic_constants_matrix_second_order
|
|
400
|
+
except Exception:
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
if c is None or spacegroup is None:
|
|
404
|
+
return False
|
|
405
|
+
|
|
406
|
+
# see Phys. Rev B 90, 224104 (2014)
|
|
407
|
+
res = False
|
|
408
|
+
if spacegroup <= 2: # Triclinic
|
|
409
|
+
res = np.count_nonzero(c < 0)
|
|
410
|
+
elif spacegroup <= 15: # Monoclinic
|
|
411
|
+
res = np.count_nonzero(c < 0)
|
|
412
|
+
elif spacegroup <= 74: # Orthorhombic
|
|
413
|
+
res = (
|
|
414
|
+
c[0][0] > 0
|
|
415
|
+
and c[0][0] * c[1][1] > c[0][1] ** 2
|
|
416
|
+
and c[0][0] * c[1][1] * c[2][2]
|
|
417
|
+
+ 2 * c[0][1] * c[0][2] * c[1][2]
|
|
418
|
+
- c[0][0] * c[1][2] ** 2
|
|
419
|
+
- c[1][1] * c[0][2] ** 2
|
|
420
|
+
- c[2][2] * c[0][1] ** 2
|
|
421
|
+
> 0
|
|
422
|
+
and c[3][3] > 0
|
|
423
|
+
and c[4][4] > 0
|
|
424
|
+
and c[5][5] > 0
|
|
425
|
+
)
|
|
426
|
+
elif spacegroup <= 88: # Tetragonal II
|
|
427
|
+
res = c[0][0] > abs(c[0][1]) and 2 * c[0][2] ** 2 < c[2][2] * (
|
|
428
|
+
c[0][0] + c[0][1]
|
|
429
|
+
)
|
|
430
|
+
elif spacegroup <= 142: # Tetragonal I
|
|
431
|
+
res = (
|
|
432
|
+
c[0][0] > abs(c[0][1])
|
|
433
|
+
and 2 * c[0][2] ** 2 < c[2][2] * (c[0][0] + c[0][1])
|
|
434
|
+
and c[3][3] > 0
|
|
435
|
+
and c[5][5] > 0
|
|
436
|
+
)
|
|
437
|
+
elif spacegroup <= 148: # rhombohedral II
|
|
438
|
+
res = (
|
|
439
|
+
c[0][0] > abs(c[0][1])
|
|
440
|
+
and c[3][3] > 0
|
|
441
|
+
and c[0][2] ** 2 < (0.5 * c[2][2] * (c[0][0] + c[0][1]))
|
|
442
|
+
and c[0][3] ** 2 + c[0][4] ** 2 < 0.5 * c[3][3] * (c[0][0] - c[0][1])
|
|
443
|
+
)
|
|
444
|
+
elif spacegroup <= 167: # rhombohedral I
|
|
445
|
+
res = (
|
|
446
|
+
c[0][0] > abs(c[0][1])
|
|
447
|
+
and c[3][3] > 0
|
|
448
|
+
and c[0][2] ** 2 < 0.5 * c[2][2] * (c[0][0] + c[0][1])
|
|
449
|
+
and c[0][3] ** 2 < 0.5 * c[3][3] * (c[0][0] - c[0][1])
|
|
450
|
+
)
|
|
451
|
+
elif spacegroup <= 194: # hexagonal I
|
|
452
|
+
res = (
|
|
453
|
+
c[0][0] > abs(c[0][1])
|
|
454
|
+
and 2 * c[0][2] ** 2 < c[2][2] * (c[0][0] + c[0][1])
|
|
455
|
+
and c[3][3] > 0
|
|
456
|
+
and c[5][5] > 0
|
|
457
|
+
)
|
|
458
|
+
else: # cubic
|
|
459
|
+
res = c[0][0] - c[0][1] > 0 and c[0][0] + 2 * c[0][1] > 0 and c[3][3] > 0
|
|
460
|
+
|
|
461
|
+
return res
|
|
462
|
+
|
|
463
|
+
def _get_maximum_fit_error(self):
|
|
464
|
+
max_error = 0.0
|
|
465
|
+
if len(self._calculations) == 0:
|
|
466
|
+
return max_error
|
|
467
|
+
|
|
468
|
+
for diagram in self.results.strain_diagrams:
|
|
469
|
+
if diagram.type == 'cross-validation':
|
|
470
|
+
error = np.amax(diagram.value)
|
|
471
|
+
max_error = error if error > max_error else max_error
|
|
472
|
+
|
|
473
|
+
return max_error
|
|
474
|
+
|
|
475
|
+
def normalize(self, archive, logger):
|
|
476
|
+
super().normalize(archive, logger)
|
|
477
|
+
|
|
478
|
+
if not self.method:
|
|
479
|
+
self.method = ElasticMethod()
|
|
480
|
+
self.inputs.append(Link(name=WORKFLOW_METHOD_NAME, section=self.method))
|
|
481
|
+
|
|
482
|
+
if not self.results:
|
|
483
|
+
self.results = ElasticResults()
|
|
484
|
+
self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results))
|
|
485
|
+
|
|
486
|
+
if self.results.is_mechanically_stable is None:
|
|
487
|
+
self.results.is_mechanically_stable = bool(
|
|
488
|
+
self._resolve_mechanical_stability()
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
if self.method.fitting_error_maximum is None:
|
|
492
|
+
self.method.fitting_error_maximum = self._get_maximum_fit_error()
|
|
493
|
+
|
|
494
|
+
SimulationWorkflowResults.normalize(self.results, archive, logger)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright The NOMAD Authors.
|
|
3
|
+
#
|
|
4
|
+
# This file is part of NOMAD. See https://nomad-lab.eu for further info.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
import numpy as np
|
|
19
|
+
from ase.eos import EquationOfState as aseEOS
|
|
20
|
+
|
|
21
|
+
from nomad.atomutils import get_volume
|
|
22
|
+
from nomad.datamodel.data import ArchiveSection
|
|
23
|
+
from nomad.units import ureg
|
|
24
|
+
from nomad.metainfo import SubSection, Section, Quantity
|
|
25
|
+
from nomad.datamodel.metainfo.workflow import Link
|
|
26
|
+
from .general import (
|
|
27
|
+
SimulationWorkflowMethod,
|
|
28
|
+
SimulationWorkflowResults,
|
|
29
|
+
ParallelSimulation,
|
|
30
|
+
WORKFLOW_METHOD_NAME,
|
|
31
|
+
WORKFLOW_RESULTS_NAME,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class EquationOfStateMethod(SimulationWorkflowMethod):
|
|
36
|
+
energy_calculator = Quantity(
|
|
37
|
+
type=str,
|
|
38
|
+
shape=[],
|
|
39
|
+
description="""
|
|
40
|
+
Name of program used to calculate energy.
|
|
41
|
+
""",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class EOSFit(ArchiveSection):
|
|
46
|
+
"""
|
|
47
|
+
Section containing results of an equation of state fit.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
m_def = Section(validate=False)
|
|
51
|
+
|
|
52
|
+
function_name = Quantity(
|
|
53
|
+
type=str,
|
|
54
|
+
shape=[],
|
|
55
|
+
description="""
|
|
56
|
+
Specifies the function used to perform the fitting of the volume-energy data. Value
|
|
57
|
+
can be one of birch_euler, birch_lagrange, birch_murnaghan, mie_gruneisen,
|
|
58
|
+
murnaghan, pack_evans_james, poirier_tarantola, tait, vinet.
|
|
59
|
+
""",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
fitted_energies = Quantity(
|
|
63
|
+
type=np.float64,
|
|
64
|
+
shape=['n_points'],
|
|
65
|
+
unit='joule',
|
|
66
|
+
description="""
|
|
67
|
+
Array of the fitted energies corresponding to each volume.
|
|
68
|
+
""",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
bulk_modulus = Quantity(
|
|
72
|
+
type=np.float64,
|
|
73
|
+
shape=[],
|
|
74
|
+
unit='pascal',
|
|
75
|
+
description="""
|
|
76
|
+
Calculated value of the bulk modulus by fitting the volume-energy data.
|
|
77
|
+
""",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
bulk_modulus_derivative = Quantity(
|
|
81
|
+
type=np.float64,
|
|
82
|
+
shape=[],
|
|
83
|
+
description="""
|
|
84
|
+
Calculated value of the pressure derivative of the bulk modulus.
|
|
85
|
+
""",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
equilibrium_volume = Quantity(
|
|
89
|
+
type=np.float64,
|
|
90
|
+
shape=[],
|
|
91
|
+
unit='m ** 3',
|
|
92
|
+
description="""
|
|
93
|
+
Calculated value of the equilibrium volume by fitting the volume-energy data.
|
|
94
|
+
""",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
equilibrium_energy = Quantity(
|
|
98
|
+
type=np.float64,
|
|
99
|
+
shape=[],
|
|
100
|
+
unit='joule',
|
|
101
|
+
description="""
|
|
102
|
+
Calculated value of the equilibrium energy by fitting the volume-energy data.
|
|
103
|
+
""",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
rms_error = Quantity(
|
|
107
|
+
type=np.float64,
|
|
108
|
+
shape=[],
|
|
109
|
+
description="""
|
|
110
|
+
Root-mean squared value of the error in the fitting.
|
|
111
|
+
""",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class EquationOfStateResults(SimulationWorkflowResults):
|
|
116
|
+
n_points = Quantity(
|
|
117
|
+
type=np.int32,
|
|
118
|
+
shape=[],
|
|
119
|
+
description="""
|
|
120
|
+
Number of volume-energy pairs in data.
|
|
121
|
+
""",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
volumes = Quantity(
|
|
125
|
+
type=np.float64,
|
|
126
|
+
shape=['n_points'],
|
|
127
|
+
unit='m ** 3',
|
|
128
|
+
description="""
|
|
129
|
+
Array of volumes per atom for which the energies are evaluated.
|
|
130
|
+
""",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
energies = Quantity(
|
|
134
|
+
type=np.float64,
|
|
135
|
+
shape=['n_points'],
|
|
136
|
+
unit='joule',
|
|
137
|
+
description="""
|
|
138
|
+
Array of energies corresponding to each volume.
|
|
139
|
+
""",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
eos_fit = SubSection(sub_section=EOSFit.m_def, repeats=True)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class EquationOfState(ParallelSimulation):
|
|
146
|
+
method = SubSection(sub_section=EquationOfStateMethod)
|
|
147
|
+
|
|
148
|
+
results = SubSection(sub_section=EquationOfStateResults)
|
|
149
|
+
|
|
150
|
+
def normalize(self, archive, logger):
|
|
151
|
+
super().normalize(archive, logger)
|
|
152
|
+
|
|
153
|
+
if not self.method:
|
|
154
|
+
self.method = EquationOfStateMethod()
|
|
155
|
+
self.inputs.append(Link(name=WORKFLOW_METHOD_NAME, section=self.method))
|
|
156
|
+
|
|
157
|
+
if not self.results:
|
|
158
|
+
self.results = EquationOfStateResults()
|
|
159
|
+
self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results))
|
|
160
|
+
|
|
161
|
+
if not self._calculations:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
if self.results.energies is None:
|
|
165
|
+
try:
|
|
166
|
+
self.results.energies = [
|
|
167
|
+
calc.energy.total.value.magnitude for calc in self._calculations
|
|
168
|
+
]
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
if self.results.volumes is None:
|
|
173
|
+
try:
|
|
174
|
+
volumes = []
|
|
175
|
+
unit = 1
|
|
176
|
+
for system in self._systems:
|
|
177
|
+
if system.atoms.lattice_vectors is not None:
|
|
178
|
+
cell = system.atoms.lattice_vectors.magnitude
|
|
179
|
+
unit = system.atoms.lattice_vectors.units
|
|
180
|
+
volumes.append(get_volume(cell))
|
|
181
|
+
self.results.volumes = np.array(volumes) * unit**3
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
if not self.results.eos_fit:
|
|
186
|
+
function_name_map = {
|
|
187
|
+
'birch_murnaghan': 'birchmurnaghan',
|
|
188
|
+
'pourier_tarantola': 'pouriertarantola',
|
|
189
|
+
'vinet': 'vinet',
|
|
190
|
+
'murnaghan': 'murnaghan',
|
|
191
|
+
'birch_euler': 'birch',
|
|
192
|
+
}
|
|
193
|
+
if self.results.volumes is not None and self.results.energies is not None:
|
|
194
|
+
# convert to ase units in order for function optimization to work
|
|
195
|
+
volumes = self.results.volumes.to('angstrom ** 3').magnitude
|
|
196
|
+
energies = self.results.energies.to('eV').magnitude
|
|
197
|
+
for function_name, ase_name in function_name_map.items():
|
|
198
|
+
try:
|
|
199
|
+
eos = aseEOS(volumes, energies, ase_name)
|
|
200
|
+
eos.fit()
|
|
201
|
+
fitted_energies = eos.func(volumes, *eos.eos_parameters)
|
|
202
|
+
rms_error = np.sqrt(np.mean((fitted_energies - energies) ** 2))
|
|
203
|
+
eos_fit = EOSFit(
|
|
204
|
+
function_name=function_name,
|
|
205
|
+
fitted_energies=fitted_energies * ureg.eV,
|
|
206
|
+
bulk_modulus=eos.B * ureg.eV / ureg.angstrom**3,
|
|
207
|
+
equilibrium_volume=eos.v0 * ureg.angstrom**3,
|
|
208
|
+
equilibrium_energy=eos.e0 * ureg.eV,
|
|
209
|
+
rms_error=rms_error,
|
|
210
|
+
)
|
|
211
|
+
self.results.eos_fit.append(eos_fit)
|
|
212
|
+
except Exception:
|
|
213
|
+
self.logger.warning('EOS fit not succesful.')
|