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.
@@ -0,0 +1,375 @@
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 nptyping import NDArray
20
+
21
+ from nomad.datamodel.data import ArchiveSection
22
+ from nomad.metainfo import SubSection, Section, Quantity, Reference, derived
23
+ from nomad.datamodel.metainfo.workflow import Link
24
+ from runschema.system import System
25
+ from .general import (
26
+ SimulationWorkflowResults,
27
+ SimulationWorkflowMethod,
28
+ SerialSimulation,
29
+ WORKFLOW_METHOD_NAME,
30
+ WORKFLOW_RESULTS_NAME,
31
+ )
32
+
33
+
34
+ class Decomposition(ArchiveSection):
35
+ """
36
+ Section containing information about the system to which an unstable compound will
37
+ decompose to.
38
+ """
39
+
40
+ m_def = Section(validate=False)
41
+
42
+ fraction = Quantity(
43
+ type=np.float64,
44
+ shape=[],
45
+ description="""
46
+ Amount of the resulting system.
47
+ """,
48
+ )
49
+
50
+ system_ref = Quantity(
51
+ type=Reference(System.m_def),
52
+ shape=[],
53
+ description="""
54
+ Reference to the resulting system.
55
+ """,
56
+ )
57
+
58
+ formula = Quantity(
59
+ type=str,
60
+ shape=[],
61
+ description="""
62
+ Chemical formula of the resulting system.
63
+ """,
64
+ )
65
+
66
+
67
+ class Stability(ArchiveSection):
68
+ """
69
+ Section containing information regarding the stability of the system.
70
+ """
71
+
72
+ m_def = Section(validate=False)
73
+
74
+ n_references = Quantity(
75
+ type=int,
76
+ shape=[],
77
+ description="""
78
+ Number of reference systems.
79
+ """,
80
+ )
81
+
82
+ systems_ref = Quantity(
83
+ type=Reference(System.m_def),
84
+ shape=['n_references'],
85
+ description="""
86
+ References to the reference systems.
87
+ """,
88
+ )
89
+
90
+ formation_energy = Quantity(
91
+ type=np.float64,
92
+ shape=[],
93
+ unit='joule',
94
+ description="""
95
+ Calculated value of the formation energy of the compound.
96
+ """,
97
+ )
98
+
99
+ delta_formation_energy = Quantity(
100
+ type=np.float64,
101
+ shape=[],
102
+ unit='joule',
103
+ description="""
104
+ Energy with respect to the convex hull.
105
+ """,
106
+ )
107
+
108
+ n_references = Quantity(
109
+ type=int,
110
+ shape=[],
111
+ description="""
112
+ Number of reference systems.
113
+ """,
114
+ )
115
+
116
+ is_stable = Quantity(
117
+ type=bool,
118
+ shape=[],
119
+ description="""
120
+ Indicates if a compound is stable.
121
+ """,
122
+ )
123
+
124
+ decomposition = SubSection(sub_section=Decomposition.m_def, repeats=True)
125
+
126
+
127
+ class ThermodynamicsResults(SimulationWorkflowResults):
128
+ n_values = Quantity(
129
+ type=int,
130
+ shape=[],
131
+ description="""
132
+ Number of thermodynamics property evaluations.
133
+ """,
134
+ )
135
+
136
+ temperature = Quantity(
137
+ type=np.float64,
138
+ shape=['n_values'],
139
+ unit='kelvin',
140
+ description="""
141
+ Specifies the temperatures at which properties such as the Helmholtz free energy
142
+ are calculated.
143
+ """,
144
+ )
145
+
146
+ pressure = Quantity(
147
+ type=np.float64,
148
+ shape=['n_values'],
149
+ unit='pascal',
150
+ description="""
151
+ Array containing the values of the pressure (one third of the trace of the stress
152
+ tensor) corresponding to each property evaluation.
153
+ """,
154
+ )
155
+
156
+ helmholtz_free_energy = Quantity(
157
+ type=np.float64,
158
+ shape=['n_values'],
159
+ unit='joule',
160
+ description="""
161
+ Helmholtz free energy per unit cell at constant volume.
162
+ """,
163
+ )
164
+
165
+ heat_capacity_c_p = Quantity(
166
+ type=np.float64,
167
+ shape=['n_values'],
168
+ unit='joule / kelvin',
169
+ description="""
170
+ Heat capacity per cell unit at constant pressure.
171
+ """,
172
+ )
173
+
174
+ heat_capacity_c_v = Quantity(
175
+ type=np.float64,
176
+ shape=['n_values'],
177
+ unit='joule / kelvin',
178
+ description="""
179
+ Heat capacity per cell unit at constant volume.
180
+ """,
181
+ )
182
+
183
+ @derived(
184
+ type=np.float64,
185
+ shape=['n_values'],
186
+ unit='joule / (kelvin * kilogram)',
187
+ description="""
188
+ Specific heat capacity at constant volume.
189
+ """,
190
+ cached=True,
191
+ )
192
+ def heat_capacity_c_v_specific(self) -> NDArray:
193
+ """Returns the specific heat capacity by dividing the heat capacity per
194
+ cell with the mass of the atoms in the cell.
195
+ """
196
+ import nomad.atomutils
197
+
198
+ workflow = self.m_parent
199
+ if not workflow._systems or not workflow._systems[0].atoms:
200
+ return
201
+ atomic_numbers = workflow._systems[0].atoms.species
202
+ mass_per_unit_cell = nomad.atomutils.get_summed_atomic_mass(atomic_numbers)
203
+ heat_capacity = self.heat_capacity_c_v
204
+ specific_heat_capacity = heat_capacity / mass_per_unit_cell
205
+
206
+ return specific_heat_capacity.magnitude
207
+
208
+ vibrational_free_energy_at_constant_volume = Quantity(
209
+ type=np.float64,
210
+ shape=['n_values'],
211
+ unit='joule',
212
+ description="""
213
+ Holds the vibrational free energy per cell unit at constant volume.
214
+ """,
215
+ )
216
+
217
+ @derived(
218
+ type=np.float64,
219
+ shape=['n_values'],
220
+ unit='joule / kilogram',
221
+ description="""
222
+ Stores the specific vibrational free energy at constant volume.
223
+ """,
224
+ cached=True,
225
+ )
226
+ def vibrational_free_energy_at_constant_volume_specific(self) -> NDArray:
227
+ import nomad.atomutils
228
+
229
+ workflow = self.m_parent
230
+ if not workflow._systems or not workflow._systems[0].atoms:
231
+ return
232
+ atomic_numbers = workflow._systems[0].atoms.species
233
+ mass_per_unit_cell = nomad.atomutils.get_summed_atomic_mass(atomic_numbers)
234
+ free_energy = self.vibrational_free_energy_at_constant_volume
235
+ specific_free_energy = free_energy / mass_per_unit_cell
236
+
237
+ return specific_free_energy.magnitude
238
+
239
+ vibrational_free_energy = Quantity(
240
+ type=np.float64,
241
+ shape=['n_values'],
242
+ unit='joule',
243
+ description="""
244
+ Calculated value of the vibrational free energy, F_vib.
245
+ """,
246
+ )
247
+
248
+ vibrational_internal_energy = Quantity(
249
+ type=np.float64,
250
+ shape=['n_values'],
251
+ unit='joule',
252
+ description="""
253
+ Calculated value of the vibrational internal energy, U_vib.
254
+ """,
255
+ )
256
+
257
+ vibrational_entropy = Quantity(
258
+ type=np.float64,
259
+ shape=['n_values'],
260
+ unit='joule / kelvin',
261
+ description="""
262
+ Calculated value of the vibrational entropy, S.
263
+ """,
264
+ )
265
+
266
+ gibbs_free_energy = Quantity(
267
+ type=np.float64,
268
+ shape=['n_values'],
269
+ unit='joule',
270
+ description="""
271
+ Calculated value of the Gibbs free energy, G.
272
+ """,
273
+ )
274
+
275
+ entropy = Quantity(
276
+ type=np.float64,
277
+ shape=['n_values'],
278
+ unit='joule / kelvin',
279
+ description="""
280
+ Calculated value of the entropy.
281
+ """,
282
+ )
283
+
284
+ enthalpy = Quantity(
285
+ type=np.float64,
286
+ shape=['n_values'],
287
+ unit='joule',
288
+ description="""
289
+ Calculated value of enthalpy.
290
+ """,
291
+ )
292
+
293
+ internal_energy = Quantity(
294
+ type=np.float64,
295
+ shape=['n_values'],
296
+ unit='joule',
297
+ description="""
298
+ Calculated value of the internal energy, U.
299
+ """,
300
+ )
301
+
302
+ stability = SubSection(sub_section=Stability.m_def, repeats=False)
303
+
304
+ def normalize(self, archive, logger):
305
+ super().normalize(archive, logger)
306
+
307
+ try:
308
+ calculations = archive.run[-1].calculation
309
+ except Exception:
310
+ calculations = []
311
+
312
+ def set_thermo_property(name):
313
+ values = []
314
+ quantity = None
315
+ for calc in calculations:
316
+ if hasattr(calc, name):
317
+ try:
318
+ quantity = calc[name]
319
+ values.append(
320
+ quantity.magnitude
321
+ if hasattr(quantity, 'magnitude')
322
+ else quantity
323
+ )
324
+ continue
325
+ except Exception:
326
+ pass
327
+ # TODO section thermodynamics should be removed
328
+ for thermo in calc.thermodynamics:
329
+ try:
330
+ quantity = thermo[name]
331
+ values.append(
332
+ quantity.magnitude
333
+ if hasattr(quantity, 'magnitude')
334
+ else quantity
335
+ )
336
+ except Exception:
337
+ pass
338
+ if len(values) == 0:
339
+ return
340
+
341
+ unit = quantity.units if hasattr(quantity, 'units') else 1.0
342
+ setattr(self, name, np.array(values) * unit)
343
+
344
+ if self.temperature is None:
345
+ set_thermo_property('temperature')
346
+
347
+ if self.helmholtz_free_energy is None:
348
+ set_thermo_property('helmholtz_free_energy')
349
+
350
+ if self.vibrational_free_energy_at_constant_volume is None:
351
+ set_thermo_property('vibrational_free_energy_at_constant_volume')
352
+
353
+ if self.heat_capacity_c_v is None:
354
+ set_thermo_property('heat_capacity_c_v')
355
+
356
+
357
+ class ThermodynamicsMethod(SimulationWorkflowMethod):
358
+ pass
359
+
360
+
361
+ class Thermodynamics(SerialSimulation):
362
+ method = SubSection(sub_section=ThermodynamicsMethod)
363
+
364
+ results = SubSection(sub_section=ThermodynamicsResults)
365
+
366
+ def normalize(self, archive, logger):
367
+ super().normalize(archive, logger)
368
+
369
+ if not self.method:
370
+ self.method = ThermodynamicsMethod()
371
+ self.inputs.append(Link(name=WORKFLOW_METHOD_NAME, section=self.method))
372
+
373
+ if not self.results:
374
+ self.results = ThermodynamicsResults()
375
+ self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results))
@@ -0,0 +1,100 @@
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
+ from nomad.metainfo import SubSection
19
+ from runschema.calculation import (
20
+ Spectra,
21
+ ElectronicStructureProvenance,
22
+ )
23
+ from .general import (
24
+ SimulationWorkflowResults,
25
+ ElectronicStructureOutputs,
26
+ SimulationWorkflowMethod,
27
+ BeyondDFT,
28
+ )
29
+ from .photon_polarization import PhotonPolarizationResults
30
+
31
+
32
+ class XSResults(SimulationWorkflowResults):
33
+ """
34
+ Groups DFT, GW and PhotonPolarization outputs: band gaps (DFT, GW), DOS (DFT, GW),
35
+ band structures (DFT, GW), spectra (PhotonPolarization). The ResultsNormalizer takes
36
+ care of adding a label 'DFT' or 'GW' in the method `get_xs_workflow_properties`.
37
+ """
38
+
39
+ dft_outputs = SubSection(
40
+ sub_section=ElectronicStructureOutputs.m_def, repeats=False
41
+ )
42
+
43
+ gw_outputs = SubSection(sub_section=ElectronicStructureOutputs.m_def, repeats=False)
44
+
45
+ spectra = SubSection(sub_section=PhotonPolarizationResults, repeats=True)
46
+
47
+
48
+ class XSMethod(SimulationWorkflowMethod):
49
+ pass
50
+
51
+
52
+ class XS(BeyondDFT):
53
+ """
54
+ The XS workflow is generated in an extra EntryArchive IF both the DFT SinglePoint
55
+ and the PhotonPolarization EntryArchives are present in the upload.
56
+ """
57
+
58
+ # TODO extend to reference a GW SinglePoint.
59
+
60
+ method = SubSection(sub_section=XSMethod)
61
+
62
+ results = SubSection(sub_section=XSResults)
63
+
64
+ def normalize(self, archive, logger):
65
+ if len(self.tasks) < 2:
66
+ logger.error(
67
+ 'Expected more than one task: DFT+PhotonPolarization or DFT+GW+PhotonPolarization.'
68
+ )
69
+ return
70
+
71
+ dft_task = self.tasks[0]
72
+ xs_tasks = [self.tasks[i] for i in range(1, len(self.tasks))]
73
+ gw_task = None
74
+ # Check if the xs_tasks contain GW SinglePoint or a list of PhotonPolarizations
75
+ if xs_tasks[0].task.m_def.name != 'PhotonPolarization':
76
+ gw_task = xs_tasks[0]
77
+ xs_tasks.pop(
78
+ 0
79
+ ) # we delete the [0] element associated with GW in case DFT+GW+PhotonPolarization workflow
80
+
81
+ if not self.results:
82
+ self.results = XSResults()
83
+
84
+ task_map = {
85
+ 'dft': dft_task,
86
+ 'gw': gw_task,
87
+ }
88
+ self.get_electronic_structure_workflow_results(task_map)
89
+
90
+ for xs in xs_tasks:
91
+ if xs.m_xpath('task.results'):
92
+ photon_results = xs.task.results
93
+ # Adding provenance to BSE method section, in addition to the existent 'photon' provenance
94
+ if xs.task.m_xpath('inputs[1].section'):
95
+ for spectra in photon_results.spectrum_polarization:
96
+ provenance = ElectronicStructureProvenance(
97
+ methodology=xs.task.inputs[1].section, label='bse'
98
+ )
99
+ spectra.m_add_sub_section(Spectra.provenance, provenance)
100
+ self.results.m_add_sub_section(XSResults.spectra, photon_results)