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,196 @@
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.metainfo import SubSection, Quantity, Reference
21
+ from nomad.datamodel.metainfo.workflow import Link
22
+ from runschema.calculation import Dos, BandStructure
23
+ from .general import (
24
+ ParallelSimulation,
25
+ SimulationWorkflowMethod,
26
+ SimulationWorkflowResults,
27
+ WORKFLOW_METHOD_NAME,
28
+ WORKFLOW_RESULTS_NAME,
29
+ )
30
+ from .thermodynamics import ThermodynamicsResults
31
+
32
+
33
+ class PhononMethod(SimulationWorkflowMethod):
34
+ force_calculator = Quantity(
35
+ type=str,
36
+ shape=[],
37
+ description="""
38
+ Name of the program used to calculate the forces.
39
+ """,
40
+ )
41
+
42
+ mesh_density = Quantity(
43
+ type=np.float64,
44
+ shape=[],
45
+ unit='1 / meter ** 3',
46
+ description="""
47
+ Density of the k-mesh for sampling.
48
+ """,
49
+ )
50
+
51
+ random_displacements = Quantity(
52
+ type=bool,
53
+ shape=[],
54
+ description="""
55
+ Identifies if displacements are made randomly.
56
+ """,
57
+ )
58
+
59
+ with_non_analytic_correction = Quantity(
60
+ type=bool,
61
+ shape=[],
62
+ description="""
63
+ Identifies if non-analytical term corrections are applied to dynamical matrix.
64
+ """,
65
+ )
66
+
67
+ with_grueneisen_parameters = Quantity(
68
+ type=bool,
69
+ shape=[],
70
+ description="""
71
+ Identifies if Grueneisen parameters are calculated.
72
+ """,
73
+ )
74
+
75
+
76
+ class PhononResults(ThermodynamicsResults):
77
+ n_imaginary_frequencies = Quantity(
78
+ type=int,
79
+ shape=[],
80
+ description="""
81
+ Number of modes with imaginary frequencies.
82
+ """,
83
+ )
84
+
85
+ n_bands = Quantity(
86
+ type=np.int32,
87
+ shape=[],
88
+ description="""
89
+ Number of phonon bands.
90
+ """,
91
+ )
92
+
93
+ n_qpoints = Quantity(
94
+ type=np.int32,
95
+ shape=[],
96
+ description="""
97
+ Number of q points for which phonon properties are evaluated.
98
+ """,
99
+ )
100
+
101
+ qpoints = Quantity(
102
+ type=np.float64,
103
+ shape=['n_qpoints', 3],
104
+ description="""
105
+ Value of the qpoints.
106
+ """,
107
+ )
108
+
109
+ group_velocity = Quantity(
110
+ type=np.float64,
111
+ shape=['n_qpoints', 'n_bands', 3],
112
+ unit='meter / second',
113
+ description="""
114
+ Calculated value of the group velocity at each qpoint.
115
+ """,
116
+ )
117
+
118
+ n_displacements = Quantity(
119
+ type=np.int32,
120
+ shape=[],
121
+ description="""
122
+ Number of independent displacements.
123
+ """,
124
+ )
125
+
126
+ n_atoms = Quantity(
127
+ type=np.int32,
128
+ shape=[],
129
+ description="""
130
+ Number of atoms in the simulation cell.
131
+ """,
132
+ )
133
+
134
+ displacements = Quantity(
135
+ type=np.float64,
136
+ shape=['n_displacements', 'n_atoms', 3],
137
+ unit='meter',
138
+ description="""
139
+ Value of the displacements applied to each atom in the simulation cell.
140
+ """,
141
+ )
142
+
143
+ dos = Quantity(
144
+ type=Reference(Dos),
145
+ shape=['n_data'],
146
+ description="""
147
+ Reference to the electronic density of states data.
148
+ """,
149
+ )
150
+
151
+ band_structure = Quantity(
152
+ type=Reference(BandStructure),
153
+ shape=['n_data'],
154
+ description="""
155
+ Reference to the electronic band structure data.
156
+ """,
157
+ )
158
+
159
+
160
+ class Phonon(ParallelSimulation):
161
+ method = SubSection(sub_section=PhononMethod)
162
+
163
+ results = SubSection(sub_section=PhononResults)
164
+
165
+ def normalize(self, archive, logger):
166
+ super().normalize(archive, logger)
167
+
168
+ if not self._calculations or not self._calculations[0].band_structure_phonon:
169
+ return
170
+
171
+ if not self.method:
172
+ self.method = PhononMethod()
173
+ self.inputs.append(Link(name=WORKFLOW_METHOD_NAME, section=self.method))
174
+
175
+ if not self.results:
176
+ self.results = PhononResults()
177
+ self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results))
178
+
179
+ ThermodynamicsResults.normalize(self.results, archive, logger)
180
+
181
+ last_calc = self._calculations[-1]
182
+
183
+ if not self.results.n_imaginary_frequencies:
184
+ n_imaginary = 0
185
+ for band_segment in last_calc.band_structure_phonon[-1].segment:
186
+ freq = band_segment.energies.magnitude
187
+ n_imaginary += np.count_nonzero(np.array(freq) < 0)
188
+ self.results.n_imaginary_frequencies = n_imaginary
189
+
190
+ SimulationWorkflowResults.normalize(self.results, archive, logger)
191
+
192
+ if not self.results.dos:
193
+ self.results.dos = last_calc.dos_phonon
194
+
195
+ if not self.results.band_structure:
196
+ self.results.band_structure = last_calc.band_structure_phonon
@@ -0,0 +1,75 @@
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.metainfo import SubSection, Quantity, Reference
21
+ from runschema.method import BSE as BSEMethodology
22
+ from runschema.calculation import Spectra
23
+ from .general import (
24
+ SimulationWorkflowResults,
25
+ SimulationWorkflowMethod,
26
+ ParallelSimulation,
27
+ )
28
+
29
+
30
+ class PhotonPolarizationResults(SimulationWorkflowResults):
31
+ """Groups all polarization outputs: spectrum."""
32
+
33
+ n_polarizations = Quantity(
34
+ type=np.int32,
35
+ description="""
36
+ Number of polarizations for the phonons used for the calculations.
37
+ """,
38
+ )
39
+
40
+ spectrum_polarization = Quantity(
41
+ type=Reference(Spectra),
42
+ shape=['n_polarizations'],
43
+ description="""
44
+ Spectrum for a given polarization of the photon.
45
+ """,
46
+ )
47
+
48
+
49
+ class PhotonPolarizationMethod(SimulationWorkflowMethod):
50
+ """Defines the full macroscopic dielectric tensor methodology: BSE method reference."""
51
+
52
+ # TODO add TDDFT methodology reference.
53
+
54
+ bse_method_ref = Quantity(
55
+ type=Reference(BSEMethodology),
56
+ description="""
57
+ BSE methodology reference.
58
+ """,
59
+ )
60
+
61
+
62
+ class PhotonPolarization(ParallelSimulation):
63
+ """The PhotonPolarization workflow is generated in an extra EntryArchive FOR all polarization
64
+ EntryArchives present in the upload. It groups them for a set of given method parameters.
65
+
66
+ This entry is also recognized as the full macroscopic dielectric tensor entry (e.g. calculated
67
+ via BSE).
68
+ """
69
+
70
+ method = SubSection(sub_section=PhotonPolarizationMethod)
71
+
72
+ results = SubSection(sub_section=PhotonPolarizationResults)
73
+
74
+ def normalize(self, archive, logger):
75
+ super().normalize(archive, logger)
@@ -0,0 +1,273 @@
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.metainfo import SubSection, Quantity, Reference
21
+ from nomad.datamodel.metainfo.workflow import Link, Task
22
+ from runschema.calculation import (
23
+ Dos,
24
+ BandStructure,
25
+ BandEnergies,
26
+ Density,
27
+ Potential,
28
+ Spectra,
29
+ )
30
+ from .general import (
31
+ SimulationWorkflowResults,
32
+ SimulationWorkflowMethod,
33
+ SimulationWorkflow,
34
+ INPUT_SYSTEM_NAME,
35
+ INPUT_METHOD_NAME,
36
+ OUTPUT_CALCULATION_NAME,
37
+ WORKFLOW_METHOD_NAME,
38
+ WORKFLOW_RESULTS_NAME,
39
+ resolve_difference,
40
+ )
41
+
42
+
43
+ class SinglePointResults(SimulationWorkflowResults):
44
+ n_scf_steps = Quantity(
45
+ type=int,
46
+ shape=[],
47
+ description="""
48
+ Number of self-consistent steps in the calculation.
49
+ """,
50
+ )
51
+
52
+ final_scf_energy_difference = Quantity(
53
+ type=np.float64,
54
+ shape=[],
55
+ unit='joule',
56
+ description="""
57
+ The difference in the energy between the last two scf steps.
58
+ """,
59
+ )
60
+
61
+ is_converged = Quantity(
62
+ type=bool,
63
+ shape=[],
64
+ description="""
65
+ Indicates if the convergence criteria were fullfilled.
66
+ """,
67
+ )
68
+
69
+ n_data = Quantity(
70
+ type=np.int32,
71
+ shape=[],
72
+ description="""
73
+ """,
74
+ )
75
+
76
+ dos = Quantity(
77
+ type=Reference(Dos),
78
+ shape=['n_data'],
79
+ description="""
80
+ Reference to the electronic density of states data.
81
+ """,
82
+ )
83
+
84
+ band_structure = Quantity(
85
+ type=Reference(BandStructure),
86
+ shape=['n_data'],
87
+ description="""
88
+ Reference to the electronic band structure data.
89
+ """,
90
+ )
91
+
92
+ eigenvalues = Quantity(
93
+ type=Reference(BandEnergies),
94
+ shape=['n_data'],
95
+ description="""
96
+ Reference to the eigenvalues.
97
+ """,
98
+ )
99
+
100
+ potential = Quantity(
101
+ type=Reference(Potential),
102
+ shape=['n_data'],
103
+ description="""
104
+ Reference to the potential data.
105
+ """,
106
+ )
107
+
108
+ density_charge = Quantity(
109
+ type=Reference(Density),
110
+ shape=['n_data'],
111
+ description="""
112
+ Reference to the charge density data.
113
+ """,
114
+ )
115
+
116
+ spectra = Quantity(
117
+ type=Reference(Spectra),
118
+ shape=['n_data'],
119
+ description="""
120
+ Reference to the spectral data.
121
+ """,
122
+ )
123
+
124
+
125
+ class SinglePointMethod(SimulationWorkflowMethod):
126
+ method = Quantity(
127
+ type=str,
128
+ shape=[],
129
+ description="""
130
+ Calculation method used.
131
+ """,
132
+ )
133
+
134
+
135
+ class SinglePoint(SimulationWorkflow):
136
+ method = SubSection(sub_section=SinglePointMethod)
137
+
138
+ results = SubSection(sub_section=SinglePointResults)
139
+
140
+ def normalize(self, archive, logger):
141
+ super().normalize(archive, logger)
142
+
143
+ if not self.tasks:
144
+ task = Task()
145
+ if self._systems:
146
+ task.m_add_sub_section(
147
+ Task.inputs, Link(name=INPUT_SYSTEM_NAME, section=self._systems[0])
148
+ )
149
+ if self._methods:
150
+ task.m_add_sub_section(
151
+ Task.inputs, Link(name=INPUT_METHOD_NAME, section=self._methods[0])
152
+ )
153
+ if self._calculations:
154
+ task.m_add_sub_section(
155
+ Task.inputs,
156
+ Link(name=OUTPUT_CALCULATION_NAME, section=self._calculations[0]),
157
+ )
158
+
159
+ self.tasks = [task]
160
+
161
+ if not self.method:
162
+ self.method = SinglePointMethod()
163
+
164
+ if not self.inputs:
165
+ self.m_add_sub_section(
166
+ SimulationWorkflow.inputs,
167
+ Link(name=WORKFLOW_METHOD_NAME, section=self.method),
168
+ )
169
+
170
+ if not self.results:
171
+ self.results = SinglePointResults()
172
+
173
+ if not self.outputs:
174
+ self.m_add_sub_section(
175
+ SimulationWorkflow.outputs,
176
+ Link(name=WORKFLOW_RESULTS_NAME, section=self.results),
177
+ )
178
+
179
+ if not self.method.method:
180
+ try:
181
+ # TODO keep extending for other SinglePoint
182
+ for method_name in ['dft', 'gw', 'bse', 'dmft']:
183
+ if self._methods[-1].m_xpath(method_name):
184
+ self.method.method = method_name.upper()
185
+ break
186
+ except Exception:
187
+ pass
188
+
189
+ if not self._calculations:
190
+ return
191
+
192
+ last_calc = self._calculations[-1]
193
+ if not self.results.n_scf_steps:
194
+ self.results.n_scf_steps = len(last_calc.scf_iteration)
195
+
196
+ energies = [
197
+ scf.energy.total.value
198
+ for scf in last_calc.scf_iteration
199
+ if scf.energy is not None and scf.energy.total is not None
200
+ ]
201
+ delta_energy = resolve_difference(energies)
202
+ if not self.results.final_scf_energy_difference and delta_energy is not None:
203
+ self.results.final_scf_energy_difference = delta_energy
204
+
205
+ if not self.results.is_converged and delta_energy is not None:
206
+ try:
207
+ threshold = self._methods[-1].scf.threshold_energy_change
208
+ self.results.is_converged = bool(delta_energy <= threshold)
209
+ except Exception:
210
+ pass
211
+
212
+ if not self.results.dos and last_calc.dos_electronic:
213
+ self.results.dos = last_calc.dos_electronic
214
+
215
+ if not self.results.band_structure and last_calc.band_structure_electronic:
216
+ self.results.band_structure = last_calc.band_structure_electronic
217
+
218
+ if not self.results.eigenvalues and last_calc.eigenvalues:
219
+ self.results.eigenvalues = last_calc.eigenvalues
220
+
221
+ if not self.results.density_charge and last_calc.density_charge:
222
+ self.results.density_charge = last_calc.density_charge
223
+
224
+ if not self.results.potential and last_calc.potential:
225
+ self.results.potential = last_calc.potential
226
+
227
+ if not self.results.spectra and last_calc.spectra:
228
+ self.results.spectra = last_calc.spectra
229
+
230
+ SimulationWorkflowResults.normalize(self.results, archive, logger)
231
+
232
+
233
+ class ParallelSimulation(SimulationWorkflow):
234
+ def normalize(self, archive, logger):
235
+ super().normalize(archive, logger)
236
+
237
+ if not self.tasks:
238
+ for n, calculation in enumerate(self._calculations):
239
+ inputs, outputs = (
240
+ [],
241
+ [Link(name=OUTPUT_CALCULATION_NAME, section=calculation)],
242
+ )
243
+ if self._calculations[n].system_ref:
244
+ inputs.append(
245
+ Link(
246
+ name=INPUT_SYSTEM_NAME,
247
+ section=self._calculations[n].system_ref,
248
+ )
249
+ )
250
+ elif len(self._calculations) == len(self._systems):
251
+ inputs.append(
252
+ Link(name=INPUT_SYSTEM_NAME, section=self._systems[n])
253
+ )
254
+ else:
255
+ continue
256
+ if self._calculations[n].method_ref:
257
+ inputs.append(
258
+ Link(
259
+ name=INPUT_METHOD_NAME,
260
+ section=self._calculations[n].method_ref,
261
+ )
262
+ )
263
+ elif len(self._calculations) == len(self._methods):
264
+ inputs.append(
265
+ Link(name=INPUT_METHOD_NAME, section=self._methods[n])
266
+ )
267
+ elif len(self._methods) == 1:
268
+ inputs.append(
269
+ Link(name=INPUT_METHOD_NAME, section=self._methods[0])
270
+ )
271
+ self.tasks.append(
272
+ Task(name=f'Calculation {n}', inputs=inputs, outputs=outputs)
273
+ )
@@ -0,0 +1,77 @@
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, Quantity, Reference
19
+ from runschema.method import TB as TBMethodology, Method
20
+ from .general import (
21
+ SimulationWorkflowResults,
22
+ ElectronicStructureOutputs,
23
+ SimulationWorkflowMethod,
24
+ SerialSimulation,
25
+ )
26
+
27
+
28
+ class FirstPrinciplesPlusTBResults(SimulationWorkflowResults):
29
+ """
30
+ Groups first principles and TB outputs: band gaps, DOS, band structures. The
31
+ ResultsNormalizer takes care of adding a label 'FirstPrinciples' or 'TB' in the method
32
+ `get_tb_workflow_properties`.
33
+ """
34
+
35
+ first_principles_outputs = SubSection(
36
+ sub_section=ElectronicStructureOutputs.m_def, repeats=False
37
+ )
38
+
39
+ tb_outputs = SubSection(sub_section=ElectronicStructureOutputs.m_def, repeats=False)
40
+
41
+
42
+ class FirstPrinciplesPlusTBMethod(SimulationWorkflowMethod):
43
+ """
44
+ Specifies both the first principles and the TB input methodologies.
45
+ """
46
+
47
+ # TODO refine this referencing
48
+ first_principles_method_ref = Quantity(
49
+ type=Reference(Method),
50
+ description="""
51
+ First principles methodology reference.
52
+ """,
53
+ )
54
+
55
+ tb_method_ref = Quantity(
56
+ type=Reference(TBMethodology),
57
+ description="""
58
+ TB methodology reference.
59
+ """,
60
+ )
61
+
62
+
63
+ class FirstPrinciplesPlusTB(SerialSimulation):
64
+ """
65
+ The TB (tight-binding) workflow is generated in an extra EntryArchive IF both
66
+ the first principles SinglePoint and the TB SinglePoint EntryArchives are present in the upload.
67
+ """
68
+
69
+ method = SubSection(sub_section=FirstPrinciplesPlusTBMethod)
70
+
71
+ results = SubSection(sub_section=FirstPrinciplesPlusTBResults)
72
+
73
+ def normalize(self, archive, logger):
74
+ if not self.results: # creates Results section if not present
75
+ self.results = FirstPrinciplesPlusTBResults()
76
+
77
+ super().normalize(archive, logger)