py4vasp-core 0.9.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.
- py4vasp/__init__.py +10 -0
- py4vasp/_analysis/mlff.py +325 -0
- py4vasp/_calculations.py +131 -0
- py4vasp/_combine/base.py +99 -0
- py4vasp/_combine/energies.py +9 -0
- py4vasp/_combine/forces.py +9 -0
- py4vasp/_combine/stresses.py +9 -0
- py4vasp/_config.py +4 -0
- py4vasp/_control/__init__.py +0 -0
- py4vasp/_control/base.py +64 -0
- py4vasp/_control/incar.py +14 -0
- py4vasp/_control/kpoints.py +14 -0
- py4vasp/_control/poscar.py +40 -0
- py4vasp/_raw/access.py +140 -0
- py4vasp/_raw/data.py +502 -0
- py4vasp/_raw/data_wrapper.py +101 -0
- py4vasp/_raw/definition.py +512 -0
- py4vasp/_raw/schema.py +202 -0
- py4vasp/_third_party/__init__.py +0 -0
- py4vasp/_third_party/graph/__init__.py +18 -0
- py4vasp/_third_party/graph/graph.py +242 -0
- py4vasp/_third_party/graph/mixin.py +107 -0
- py4vasp/_third_party/graph/plot.py +55 -0
- py4vasp/_third_party/graph/series.py +127 -0
- py4vasp/_third_party/interactive.py +34 -0
- py4vasp/_third_party/view/__init__.py +4 -0
- py4vasp/_third_party/view/mixin.py +40 -0
- py4vasp/_third_party/view/view.py +223 -0
- py4vasp/_util/check.py +25 -0
- py4vasp/_util/convert.py +81 -0
- py4vasp/_util/documentation.py +12 -0
- py4vasp/_util/import_.py +27 -0
- py4vasp/_util/index.py +373 -0
- py4vasp/_util/parser.py +323 -0
- py4vasp/_util/reader.py +26 -0
- py4vasp/_util/select.py +419 -0
- py4vasp/calculation/_CONTCAR.py +139 -0
- py4vasp/calculation/_OSZICAR.py +103 -0
- py4vasp/calculation/__init__.py +88 -0
- py4vasp/calculation/_band.py +197 -0
- py4vasp/calculation/_bandgap.py +283 -0
- py4vasp/calculation/_base.py +333 -0
- py4vasp/calculation/_born_effective_charge.py +52 -0
- py4vasp/calculation/_class.py +161 -0
- py4vasp/calculation/_density.py +350 -0
- py4vasp/calculation/_dielectric_function.py +177 -0
- py4vasp/calculation/_dielectric_tensor.py +76 -0
- py4vasp/calculation/_dispersion.py +143 -0
- py4vasp/calculation/_dos.py +158 -0
- py4vasp/calculation/_elastic_modulus.py +62 -0
- py4vasp/calculation/_energy.py +204 -0
- py4vasp/calculation/_fatband.py +58 -0
- py4vasp/calculation/_force.py +109 -0
- py4vasp/calculation/_force_constant.py +54 -0
- py4vasp/calculation/_internal_strain.py +59 -0
- py4vasp/calculation/_kpoint.py +281 -0
- py4vasp/calculation/_magnetism.py +317 -0
- py4vasp/calculation/_pair_correlation.py +121 -0
- py4vasp/calculation/_phonon.py +57 -0
- py4vasp/calculation/_phonon_band.py +92 -0
- py4vasp/calculation/_phonon_dos.py +97 -0
- py4vasp/calculation/_piezoelectric_tensor.py +75 -0
- py4vasp/calculation/_polarization.py +43 -0
- py4vasp/calculation/_potential.py +167 -0
- py4vasp/calculation/_projector.py +433 -0
- py4vasp/calculation/_selection.py +14 -0
- py4vasp/calculation/_slice.py +73 -0
- py4vasp/calculation/_stress.py +85 -0
- py4vasp/calculation/_structure.py +420 -0
- py4vasp/calculation/_system.py +17 -0
- py4vasp/calculation/_topology.py +185 -0
- py4vasp/calculation/_velocity.py +107 -0
- py4vasp/calculation/_workfunction.py +76 -0
- py4vasp/combine.py +5 -0
- py4vasp/control.py +11 -0
- py4vasp/exception.py +65 -0
- py4vasp/raw.py +20 -0
- py4vasp/scripts/__init__.py +0 -0
- py4vasp/scripts/error_analysis.py +145 -0
- py4vasp_core-0.9.0.dist-info/LICENSE.txt +178 -0
- py4vasp_core-0.9.0.dist-info/METADATA +109 -0
- py4vasp_core-0.9.0.dist-info/RECORD +84 -0
- py4vasp_core-0.9.0.dist-info/WHEEL +4 -0
- py4vasp_core-0.9.0.dist-info/entry_points.txt +3 -0
py4vasp/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Copyright © VASP Software GmbH,
|
|
2
|
+
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
3
|
+
from py4vasp._analysis.mlff import MLFFErrorAnalysis
|
|
4
|
+
from py4vasp._calculations import Calculations
|
|
5
|
+
from py4vasp._third_party.graph import plot
|
|
6
|
+
from py4vasp._third_party.interactive import set_error_handling
|
|
7
|
+
from py4vasp.calculation._class import Calculation
|
|
8
|
+
|
|
9
|
+
__version__ = "0.9.0"
|
|
10
|
+
set_error_handling("Minimal")
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Copyright © VASP Software GmbH,
|
|
2
|
+
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
import py4vasp
|
|
9
|
+
from py4vasp import exception
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MLFFErrorAnalysis:
|
|
13
|
+
"""A class to handle the error analysis of MLFF calculations.
|
|
14
|
+
|
|
15
|
+
This class is used to perform error analysis of MLFF calculations. It
|
|
16
|
+
provides methods to calculate the error in energy, forces and stresses
|
|
17
|
+
from MLFF and DFT calculations. See the documentation for the methods
|
|
18
|
+
for more details on the type of error calculated.
|
|
19
|
+
|
|
20
|
+
Notes
|
|
21
|
+
-----
|
|
22
|
+
The class is designed to be instantiated using the class methods
|
|
23
|
+
:meth:`from_paths` and :meth:`from_files`. Please use these methods instead
|
|
24
|
+
of directly calling the class.
|
|
25
|
+
|
|
26
|
+
Examples
|
|
27
|
+
--------
|
|
28
|
+
>>> from py4vasp import MLFFErrorAnalysis
|
|
29
|
+
>>> mlff_error_analysis = MLFFErrorAnalysis.from_paths(
|
|
30
|
+
... dft_data="path/to/dft/data",
|
|
31
|
+
... mlff_data="path/to/mlff/data",
|
|
32
|
+
... )
|
|
33
|
+
>>> energy_error = mlff_error_analysis.get_energy_error_per_atom()
|
|
34
|
+
>>> force_error = mlff_error_analysis.get_force_rmse()
|
|
35
|
+
>>> stress_error = mlff_error_analysis.get_stress_rmse()
|
|
36
|
+
>>> # If you want to normalize the error by the number of configurations
|
|
37
|
+
>>> energy_error = mlff_error_analysis.get_energy_error_per_atom(
|
|
38
|
+
... normalize_by_configurations=True
|
|
39
|
+
... )
|
|
40
|
+
>>> force_error = mlff_error_analysis.get_force_rmse(
|
|
41
|
+
... normalize_by_configurations=True
|
|
42
|
+
... )
|
|
43
|
+
>>> stress_error = mlff_error_analysis.get_stress_rmse(
|
|
44
|
+
... normalize_by_configurations=True
|
|
45
|
+
... )
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, *args, **kwargs):
|
|
49
|
+
self.mlff = SimpleNamespace()
|
|
50
|
+
self.dft = SimpleNamespace()
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def _from_data(cls, _calculations):
|
|
54
|
+
mlff_error_analysis = cls(_internal=True)
|
|
55
|
+
mlff_error_analysis._calculations = _calculations
|
|
56
|
+
set_appropriate_attrs(mlff_error_analysis)
|
|
57
|
+
return mlff_error_analysis
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_paths(cls, dft_data, mlff_data):
|
|
61
|
+
"""Create an instance of MLFFErrorAnalysis from paths to the data.
|
|
62
|
+
|
|
63
|
+
Starting from paths for DFT and MLFF data, this method creates an
|
|
64
|
+
instance of MLFFErrorAnalysis. The paths are used to read the data
|
|
65
|
+
from the files.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
dft_data : str or pathlib.Path
|
|
70
|
+
Path to the DFT data. Accepts wildcards.
|
|
71
|
+
mlff_data : str or pathlib.Path
|
|
72
|
+
Path to the MLFF data. Accepts wildcards.
|
|
73
|
+
"""
|
|
74
|
+
mlff_error_analysis = cls(_internal=True)
|
|
75
|
+
calculations = py4vasp.Calculations.from_paths(
|
|
76
|
+
dft_data=dft_data, mlff_data=mlff_data
|
|
77
|
+
)
|
|
78
|
+
mlff_error_analysis._calculations = calculations
|
|
79
|
+
set_appropriate_attrs(mlff_error_analysis)
|
|
80
|
+
return mlff_error_analysis
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_files(cls, dft_data, mlff_data):
|
|
84
|
+
"""Create an instance of MLFFErrorAnalysis from files.
|
|
85
|
+
|
|
86
|
+
Starting from files for DFT and MLFF data, this method creates an
|
|
87
|
+
instance of MLFFErrorAnalysis. The files are used to read the data
|
|
88
|
+
from the files.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
dft_data : str or pathlib.Path
|
|
93
|
+
Path to the DFT data. Accepts wildcards.
|
|
94
|
+
mlff_data : str or pathlib.Path
|
|
95
|
+
Path to the MLFF data. Accepts wildcards.
|
|
96
|
+
"""
|
|
97
|
+
mlff_error_analysis = cls(_internal=True)
|
|
98
|
+
calculations = py4vasp.Calculations.from_files(
|
|
99
|
+
dft_data=dft_data, mlff_data=mlff_data
|
|
100
|
+
)
|
|
101
|
+
mlff_error_analysis._calculations = calculations
|
|
102
|
+
set_appropriate_attrs(mlff_error_analysis)
|
|
103
|
+
return mlff_error_analysis
|
|
104
|
+
|
|
105
|
+
def get_energy_error_per_atom(self, normalize_by_configurations=False):
|
|
106
|
+
"""Get the error in energy per atom.
|
|
107
|
+
|
|
108
|
+
This method calculates the error in energy per atom between the MLFF
|
|
109
|
+
and DFT calculations. The error is calculated as
|
|
110
|
+
:math:`\\frac{E_{MLFF} - E_{DFT}}{N_{ions}}`, where :math:`E_{MLFF}`
|
|
111
|
+
and :math:`E_{DFT}` are the energies from the MLFF and DFT calculations
|
|
112
|
+
respectively, and :math:`N_{ions}` is the number of ions in the
|
|
113
|
+
structure. If ``normalize_by_configurations`` is set to ``True``, the
|
|
114
|
+
error is averaged over the number of configurations.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
normalize_by_configurations : bool, optional
|
|
119
|
+
If set to ``True``, the error is averaged over the number of
|
|
120
|
+
configurations. Defaults to ``False``.
|
|
121
|
+
"""
|
|
122
|
+
error = (self.mlff.energies - self.dft.energies) / self.dft.nions
|
|
123
|
+
if normalize_by_configurations:
|
|
124
|
+
error = np.sum(np.abs(error), axis=-1) / self.dft.nconfig
|
|
125
|
+
return error
|
|
126
|
+
|
|
127
|
+
def _get_rmse(self, dft_quantity, mlff_quantity, degrees_of_freedom):
|
|
128
|
+
norm_error = np.linalg.norm(dft_quantity - mlff_quantity, axis=-1)
|
|
129
|
+
error = np.sqrt(np.sum(norm_error**2, axis=-1) / degrees_of_freedom)
|
|
130
|
+
return error
|
|
131
|
+
|
|
132
|
+
def get_force_rmse(self, normalize_by_configurations=False):
|
|
133
|
+
"""Get the root mean square error in forces.
|
|
134
|
+
|
|
135
|
+
This method calculates the root mean square error in forces between the
|
|
136
|
+
MLFF and DFT calculations. The error is calculated as
|
|
137
|
+
:math:`\\sqrt{\\frac{\\sum_{i=1}^{N_{ions}}{\\sum_{j=1}^{3}{(F_{MLFF} - F_{DFT})^2}}}{3N_{ions}}}`,
|
|
138
|
+
where :math:`F_{MLFF}` and :math:`F_{DFT}` are the forces from the MLFF
|
|
139
|
+
and DFT calculations respectively, and :math:`N_{ions}` is the number
|
|
140
|
+
of ions in the structure. If ``normalize_by_configurations`` is set to
|
|
141
|
+
``True``, the error is averaged over the number of configurations.
|
|
142
|
+
|
|
143
|
+
Parameters
|
|
144
|
+
----------
|
|
145
|
+
normalize_by_configurations : bool, optional
|
|
146
|
+
If set to ``True``, the error is averaged over the number of
|
|
147
|
+
configurations. Defaults to ``False``.
|
|
148
|
+
"""
|
|
149
|
+
deg_freedom = 3 * self.dft.nions
|
|
150
|
+
error = self._get_rmse(self.dft.forces, self.mlff.forces, deg_freedom)
|
|
151
|
+
if normalize_by_configurations:
|
|
152
|
+
error = np.sum(error, axis=-1) / self.dft.nconfig
|
|
153
|
+
return error
|
|
154
|
+
|
|
155
|
+
def get_stress_rmse(self, normalize_by_configurations=False):
|
|
156
|
+
"""Get the root mean square error in stresses.
|
|
157
|
+
|
|
158
|
+
This method calculates the root mean square error in stresses between
|
|
159
|
+
the MLFF and DFT calculations. The error is calculated as
|
|
160
|
+
:math:`\\sqrt{\\frac{\\sum_{i=1}^{6}{(\\sigma_{MLFF} - \\sigma_{DFT})^2}}{6}}`,
|
|
161
|
+
where :math:`\\sigma_{MLFF}` and :math:`\\sigma_{DFT}` are the stresses
|
|
162
|
+
from the MLFF and DFT calculations respectively. If
|
|
163
|
+
``normalize_by_configurations`` is set to ``True``, the error is
|
|
164
|
+
averaged over the number of configurations.
|
|
165
|
+
"""
|
|
166
|
+
deg_freedom = 6
|
|
167
|
+
dft_stresses = np.triu(self.dft.stresses)
|
|
168
|
+
mlff_stresses = np.triu(self.mlff.stresses)
|
|
169
|
+
error = self._get_rmse(dft_stresses, mlff_stresses, deg_freedom)
|
|
170
|
+
if normalize_by_configurations:
|
|
171
|
+
error = np.sum(error, axis=-1) / self.dft.nconfig
|
|
172
|
+
return error
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def set_appropriate_attrs(cls):
|
|
176
|
+
set_paths_and_files(cls)
|
|
177
|
+
set_number_of_ions(cls)
|
|
178
|
+
set_number_of_configurations(cls)
|
|
179
|
+
set_energies(cls)
|
|
180
|
+
set_force_related_attributes(cls)
|
|
181
|
+
set_stresses(cls)
|
|
182
|
+
validate_data(cls)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def validate_data(cls):
|
|
186
|
+
"""Validate the data passed to the class.
|
|
187
|
+
|
|
188
|
+
This method validates the data passed to the class. It checks if the
|
|
189
|
+
number of ions, lattice vectors and positions are consistent between the
|
|
190
|
+
DFT and MLFF calculations. If not, it raises an exception.
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
np.testing.assert_almost_equal(cls.dft.positions, cls.mlff.positions)
|
|
194
|
+
np.testing.assert_almost_equal(
|
|
195
|
+
cls.dft.lattice_vectors, cls.mlff.lattice_vectors
|
|
196
|
+
)
|
|
197
|
+
np.testing.assert_almost_equal(cls.dft.nions, cls.mlff.nions)
|
|
198
|
+
except AssertionError:
|
|
199
|
+
raise exception.IncorrectUsage(
|
|
200
|
+
"""\
|
|
201
|
+
Please pass a consistent set of data between DFT and MLFF calculations."""
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def set_number_of_configurations(cls):
|
|
206
|
+
"""Set the number of configurations in the data.
|
|
207
|
+
|
|
208
|
+
This method sets the number of configurations in the data. It uses the
|
|
209
|
+
number of calculations performed to set the number of configurations.
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
cls : MLFFErrorAnalysis
|
|
214
|
+
An instance of MLFFErrorAnalysis.
|
|
215
|
+
"""
|
|
216
|
+
number_of_calculations = cls._calculations.number_of_calculations()
|
|
217
|
+
cls.dft.nconfig = number_of_calculations["dft_data"]
|
|
218
|
+
cls.mlff.nconfig = number_of_calculations["mlff_data"]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def set_number_of_ions(cls):
|
|
222
|
+
"""Set the number of ions in the data.
|
|
223
|
+
|
|
224
|
+
This method sets the number of ions in the data. It uses the number of
|
|
225
|
+
elements in the structures to set the number of ions.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
cls : MLFFErrorAnalysis
|
|
230
|
+
An instance of MLFFErrorAnalysis.
|
|
231
|
+
"""
|
|
232
|
+
force_data = cls._calculations.forces.read()
|
|
233
|
+
structures_dft = _dict_to_list(force_data["dft_data"], "structure")
|
|
234
|
+
structures_mlff = _dict_to_list(force_data["mlff_data"], "structure")
|
|
235
|
+
elements_dft = _dict_to_array(structures_dft, "elements")
|
|
236
|
+
elements_mlff = _dict_to_array(structures_mlff, "elements")
|
|
237
|
+
nions_dft = np.array([len(_elements) for _elements in elements_dft])
|
|
238
|
+
nions_mlff = np.array([len(_elements) for _elements in elements_mlff])
|
|
239
|
+
cls.dft.nions = nions_dft
|
|
240
|
+
cls.mlff.nions = nions_mlff
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def set_paths_and_files(cls):
|
|
244
|
+
"""Set the paths and files for the data.
|
|
245
|
+
|
|
246
|
+
This method sets the paths and files for the data. It uses the
|
|
247
|
+
:meth:`Calculations.paths` and :meth:`Calculations.files` methods to set
|
|
248
|
+
the paths and files.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
cls : MLFFErrorAnalysis
|
|
253
|
+
An instance of MLFFErrorAnalysis.
|
|
254
|
+
"""
|
|
255
|
+
paths = cls._calculations.paths()
|
|
256
|
+
cls.dft.paths = paths["dft_data"]
|
|
257
|
+
cls.mlff.paths = paths["mlff_data"]
|
|
258
|
+
if hasattr(cls._calculations, "_files"):
|
|
259
|
+
files = cls._calculations.files()
|
|
260
|
+
cls.dft.files = files["dft_data"]
|
|
261
|
+
cls.mlff.files = files["mlff_data"]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def set_energies(cls):
|
|
265
|
+
"""Set the energies for the data.
|
|
266
|
+
|
|
267
|
+
This method sets the energies for the data. It uses the
|
|
268
|
+
:meth:`Calculations.energies` method to set the energies.
|
|
269
|
+
|
|
270
|
+
Parameters
|
|
271
|
+
----------
|
|
272
|
+
cls : MLFFErrorAnalysis
|
|
273
|
+
An instance of MLFFErrorAnalysis.
|
|
274
|
+
"""
|
|
275
|
+
tag = "free energy TOTEN"
|
|
276
|
+
energies_data = cls._calculations.energies.read()
|
|
277
|
+
cls.mlff.energies = _dict_to_array(energies_data["mlff_data"], tag)
|
|
278
|
+
cls.dft.energies = _dict_to_array(energies_data["dft_data"], tag)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _dict_to_array(data: Dict, key: str) -> np.ndarray:
|
|
282
|
+
return np.array([_data[key] for _data in data])
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _dict_to_list(data: Dict, key: str) -> list:
|
|
286
|
+
return [_data[key] for _data in data]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def set_force_related_attributes(cls):
|
|
290
|
+
"""Set the force related attributes for the data.
|
|
291
|
+
|
|
292
|
+
This method sets the force related attributes for the data. It uses the
|
|
293
|
+
:meth:`Calculations.forces` method to set the forces, lattice vectors and
|
|
294
|
+
positions.
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
cls : MLFFErrorAnalysis
|
|
299
|
+
An instance of MLFFErrorAnalysis.
|
|
300
|
+
"""
|
|
301
|
+
force_data = cls._calculations.forces.read()
|
|
302
|
+
cls.dft.forces = _dict_to_array(force_data["dft_data"], "forces")
|
|
303
|
+
cls.mlff.forces = _dict_to_array(force_data["mlff_data"], "forces")
|
|
304
|
+
dft_structures = _dict_to_list(force_data["dft_data"], "structure")
|
|
305
|
+
mlff_structures = _dict_to_list(force_data["mlff_data"], "structure")
|
|
306
|
+
cls.dft.lattice_vectors = _dict_to_array(dft_structures, "lattice_vectors")
|
|
307
|
+
cls.mlff.lattice_vectors = _dict_to_array(mlff_structures, "lattice_vectors")
|
|
308
|
+
cls.dft.positions = _dict_to_array(dft_structures, "positions")
|
|
309
|
+
cls.mlff.positions = _dict_to_array(mlff_structures, "positions")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def set_stresses(cls):
|
|
313
|
+
"""Set the stresses for the data.
|
|
314
|
+
|
|
315
|
+
This method sets the stresses for the data. It uses the
|
|
316
|
+
:meth:`Calculations.stresses` method to set the stresses.
|
|
317
|
+
|
|
318
|
+
Parameters
|
|
319
|
+
----------
|
|
320
|
+
cls : MLFFErrorAnalysis
|
|
321
|
+
An instance of MLFFErrorAnalysis.
|
|
322
|
+
"""
|
|
323
|
+
stress_data = cls._calculations.stresses.read()
|
|
324
|
+
cls.dft.stresses = _dict_to_array(stress_data["dft_data"], "stress")
|
|
325
|
+
cls.mlff.stresses = _dict_to_array(stress_data["mlff_data"], "stress")
|
py4vasp/_calculations.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Copyright © VASP Software GmbH,
|
|
2
|
+
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
3
|
+
import inspect
|
|
4
|
+
import pathlib
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from py4vasp import combine, exception
|
|
8
|
+
from py4vasp._util import convert
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Calculations:
|
|
12
|
+
"""A class to handle multiple Calculations all at once.
|
|
13
|
+
|
|
14
|
+
This class combines the functionality of the Calculation class for more than one
|
|
15
|
+
calculation. Create a Calculations object using either a wildcard for a set of
|
|
16
|
+
paths or files or pass in paths and files directly. Then you can access the
|
|
17
|
+
properties of all calculations via the attributes of the object.
|
|
18
|
+
|
|
19
|
+
Examples
|
|
20
|
+
--------
|
|
21
|
+
>>> calcs = Calculations.from_paths(calc1="path_to_calc1", calc2="path_to_calc2")
|
|
22
|
+
>>> calcs.energies.read() # returns a dictionary with the energies of calc1 and calc2
|
|
23
|
+
>>> calcs.forces.read() # returns a dictionary with the forces of calc1 and calc2
|
|
24
|
+
>>> calcs.stresses.read() # returns a dictionary with the stresses of calc1 and calc2
|
|
25
|
+
|
|
26
|
+
Notes
|
|
27
|
+
-----
|
|
28
|
+
To create new instances, you should use the classmethod :meth:`from_paths` or
|
|
29
|
+
:meth:`from_files`. This will ensure that the paths to your VASP calculations are
|
|
30
|
+
properly set and all features work as intended. Note that this is an alpha version
|
|
31
|
+
and the API might change in the future.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args, **kwargs):
|
|
35
|
+
if not kwargs.get("_internal"):
|
|
36
|
+
message = """\
|
|
37
|
+
Please setup new CompareCalculations instance using the classmethod CompareCalculations.from_paths()
|
|
38
|
+
or CompareCalculations.from_files() instead of the constructor CompareCalculations()."""
|
|
39
|
+
raise exception.IncorrectUsage(message)
|
|
40
|
+
|
|
41
|
+
def _path_finder(**kwargs):
|
|
42
|
+
for key, value in kwargs.items():
|
|
43
|
+
if not (isinstance(value, pathlib.Path) or isinstance(value, str)):
|
|
44
|
+
message = """\
|
|
45
|
+
Please provide a path to a VASP calculation as a string or pathlib.Path."""
|
|
46
|
+
raise exception.IncorrectUsage(message)
|
|
47
|
+
paths = pathlib.Path(value).expanduser().absolute()
|
|
48
|
+
if "*" in paths.as_posix():
|
|
49
|
+
paths = sorted(list(paths.parent.glob(paths.name)))
|
|
50
|
+
else:
|
|
51
|
+
paths = [paths]
|
|
52
|
+
yield key, paths
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_paths(cls, **kwargs):
|
|
56
|
+
"""Set up a Calculations object for paths.
|
|
57
|
+
|
|
58
|
+
Setup a calculation for paths by passing in a dictionary with the name of the
|
|
59
|
+
calculation as key and the path to the calculation as value.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
**kwargs : Dict[str, str or pathlib.Path]
|
|
64
|
+
A dictionary with the name of the calculation as key and the path to the
|
|
65
|
+
calculation as value. Wildcards are allowed.
|
|
66
|
+
"""
|
|
67
|
+
calculations = cls(_internal=True)
|
|
68
|
+
calculations._paths = {}
|
|
69
|
+
for key, paths in cls._path_finder(**kwargs):
|
|
70
|
+
calculations._paths[key] = paths
|
|
71
|
+
calculations = _add_all_combination_classes(
|
|
72
|
+
calculations, _add_attribute_from_path
|
|
73
|
+
)
|
|
74
|
+
return calculations
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def from_files(cls, **kwargs):
|
|
78
|
+
"""Set up a Calculations object from files.
|
|
79
|
+
|
|
80
|
+
Setup a calculation for files by passing in a dictionary with the name of the
|
|
81
|
+
calculation as key and the path to the calculation as value. Note that this
|
|
82
|
+
limits the amount of information, you have access to, so prefer creating the
|
|
83
|
+
instance with the :meth:`from_paths` if possible.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
**kwargs : Dict[str, str or pathlib.Path]
|
|
88
|
+
A dictionary with the name of the calculation as key and the files to the
|
|
89
|
+
calculation as value. Wildcards are allowed.
|
|
90
|
+
"""
|
|
91
|
+
calculations = cls(_internal=True)
|
|
92
|
+
calculations._paths = {}
|
|
93
|
+
calculations._files = {}
|
|
94
|
+
for key, paths in cls._path_finder(**kwargs):
|
|
95
|
+
basedir_paths = [path.parent for path in paths]
|
|
96
|
+
calculations._paths[key] = basedir_paths
|
|
97
|
+
calculations._files[key] = paths
|
|
98
|
+
calculations = _add_all_combination_classes(
|
|
99
|
+
calculations, _add_attribute_from_file
|
|
100
|
+
)
|
|
101
|
+
return calculations
|
|
102
|
+
|
|
103
|
+
def paths(self) -> Dict[str, List[pathlib.Path]]:
|
|
104
|
+
"""Return the paths of the calculations."""
|
|
105
|
+
return self._paths
|
|
106
|
+
|
|
107
|
+
def files(self) -> Dict[str, List[pathlib.Path]]:
|
|
108
|
+
"""Return the files of the calculations."""
|
|
109
|
+
return self._files
|
|
110
|
+
|
|
111
|
+
def number_of_calculations(self) -> Dict[str, int]:
|
|
112
|
+
"""Return the number of calculations for each calculation."""
|
|
113
|
+
return {key: len(value) for key, value in self._paths.items()}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _add_attribute_from_path(calc, class_):
|
|
117
|
+
instance = class_.from_paths(calc.paths())
|
|
118
|
+
setattr(calc, convert.quantity_name(class_.__name__), instance)
|
|
119
|
+
return calc
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _add_attribute_from_file(calc, class_):
|
|
123
|
+
instance = class_.from_files(calc.files())
|
|
124
|
+
setattr(calc, convert.quantity_name(class_.__name__), instance)
|
|
125
|
+
return calc
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _add_all_combination_classes(calc, add_single_class):
|
|
129
|
+
for _, class_ in inspect.getmembers(combine, inspect.isclass):
|
|
130
|
+
calc = add_single_class(calc, class_)
|
|
131
|
+
return calc
|
py4vasp/_combine/base.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Copyright © VASP Software GmbH,
|
|
2
|
+
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
3
|
+
import inspect
|
|
4
|
+
import pathlib
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from py4vasp import calculation, exception
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _match_combine_with_refinement(combine_name: str):
|
|
11
|
+
combine_to_refinement_name = {
|
|
12
|
+
"Energies": "energy",
|
|
13
|
+
"Forces": "force",
|
|
14
|
+
"Stresses": "stress",
|
|
15
|
+
}
|
|
16
|
+
return getattr(calculation, combine_to_refinement_name[combine_name])
|
|
17
|
+
# for _, class_ in inspect.getmembers(data_depr, inspect.isclass):
|
|
18
|
+
# if class_.__name__ == combine_to_refinement_name[combine_name]:
|
|
19
|
+
# return class_
|
|
20
|
+
# else:
|
|
21
|
+
# raise exception.IncorrectUsage(
|
|
22
|
+
# f"Could not find refinement class for {combine_name}."
|
|
23
|
+
# )
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseCombine:
|
|
27
|
+
"""A class to handle multiple refinements all at once.
|
|
28
|
+
|
|
29
|
+
This class combines the functionality of the refinement class for more than one
|
|
30
|
+
refinement. Create a BaseCombine object using either a wildcard for a set of
|
|
31
|
+
paths or files or pass in paths and files directly. Then you can access the
|
|
32
|
+
properties of all refinements via the attributes of the object.
|
|
33
|
+
|
|
34
|
+
Notes
|
|
35
|
+
-----
|
|
36
|
+
To create new instances, you should use the classmethod :meth:`from_paths` or
|
|
37
|
+
:meth:`from_files`. This will ensure that the paths to your VASP calculations are
|
|
38
|
+
properly set and all features work as intended. Note that this is an alpha version
|
|
39
|
+
and the API might change in the future.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_paths(cls, paths: Dict[str, List[pathlib.Path]]):
|
|
47
|
+
"""Set up a BaseCombine object for paths.
|
|
48
|
+
|
|
49
|
+
Setup the object for paths by passing in a dictionary with the name of the
|
|
50
|
+
calculation as key and the path to the calculation as value.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
paths : Dict[str, List[pathlib.Path]]
|
|
55
|
+
A dictionary with the name of the calculation as key and the path to the
|
|
56
|
+
calculation as value.
|
|
57
|
+
"""
|
|
58
|
+
base = cls()
|
|
59
|
+
refinement = _match_combine_with_refinement(cls.__name__)
|
|
60
|
+
setattr(base, f"_{cls.__name__.lower()}", {})
|
|
61
|
+
for key, path in paths.items():
|
|
62
|
+
all_refinements = [refinement.from_path(_path) for _path in path]
|
|
63
|
+
base.__getattribute__(f"_{cls.__name__.lower()}")[key] = all_refinements
|
|
64
|
+
return base
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_files(cls, files: Dict[str, List[pathlib.Path]]):
|
|
68
|
+
"""Set up a BaseCombine object for files.
|
|
69
|
+
|
|
70
|
+
Setup the object for files by passing in a dictionary with the name of the
|
|
71
|
+
calculation as key and the path to the calculation as value.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
files : Dict[str, List[pathlib.Path]]
|
|
76
|
+
A dictionary with the name of the calculation as key and the path to the
|
|
77
|
+
calculation as value.
|
|
78
|
+
"""
|
|
79
|
+
base = cls()
|
|
80
|
+
refinement = _match_combine_with_refinement(cls.__name__)
|
|
81
|
+
setattr(base, f"_{cls.__name__.lower()}", {})
|
|
82
|
+
for key, file in files.items():
|
|
83
|
+
all_refinements = [refinement.from_file(_file) for _file in file]
|
|
84
|
+
base.__getattribute__(f"_{cls.__name__.lower()}")[key] = all_refinements
|
|
85
|
+
return base
|
|
86
|
+
|
|
87
|
+
def _to_dict(self, *args, **kwargs):
|
|
88
|
+
_data = {}
|
|
89
|
+
_class_name = f"_{self.__class__.__name__.lower()}"
|
|
90
|
+
keyval_refinements = self.__getattribute__(_class_name).items()
|
|
91
|
+
for key, refinement in keyval_refinements:
|
|
92
|
+
_data[key] = [
|
|
93
|
+
_refinement.read(*args, **kwargs) for _refinement in refinement
|
|
94
|
+
]
|
|
95
|
+
return _data
|
|
96
|
+
|
|
97
|
+
def read(self, *args, **kwargs):
|
|
98
|
+
"""Read the data from the :meth:`read` method of the refinement class."""
|
|
99
|
+
return self._to_dict(*args, **kwargs)
|
py4vasp/_config.py
ADDED
|
File without changes
|
py4vasp/_control/base.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Copyright © VASP Software GmbH,
|
|
2
|
+
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InputFile:
|
|
7
|
+
def __init__(self, path):
|
|
8
|
+
self._path = path
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
def from_string(cls, string, path=None):
|
|
12
|
+
"""Generate the file from a given string and store it.
|
|
13
|
+
|
|
14
|
+
If no path is provided, the content of the file is stored in memory otherwise
|
|
15
|
+
it is stored in the path.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
string : str
|
|
20
|
+
Content of the file.
|
|
21
|
+
path : str or Path
|
|
22
|
+
If provided should define where the file is stored.
|
|
23
|
+
"""
|
|
24
|
+
obj = cls(path)
|
|
25
|
+
obj.write(string)
|
|
26
|
+
return obj
|
|
27
|
+
|
|
28
|
+
def print(self):
|
|
29
|
+
"Write the contents of the file to screen."
|
|
30
|
+
print(self)
|
|
31
|
+
|
|
32
|
+
def write(self, string):
|
|
33
|
+
"Store the given string in the file."
|
|
34
|
+
if self._path is not None:
|
|
35
|
+
self._write_to_file(string)
|
|
36
|
+
else:
|
|
37
|
+
self._write_to_memory(string)
|
|
38
|
+
|
|
39
|
+
def read(self):
|
|
40
|
+
"Return the content of the file as a string."
|
|
41
|
+
if self._path is not None:
|
|
42
|
+
return self._read_from_file()
|
|
43
|
+
else:
|
|
44
|
+
return self._read_from_memory()
|
|
45
|
+
|
|
46
|
+
def _repr_pretty_(self, p, cycle):
|
|
47
|
+
p.text(str(self))
|
|
48
|
+
|
|
49
|
+
def _write_to_file(self, string):
|
|
50
|
+
with open(Path(self._path) / self.__class__.__name__, "w") as file:
|
|
51
|
+
file.write(string)
|
|
52
|
+
|
|
53
|
+
def _write_to_memory(self, string):
|
|
54
|
+
self._content = string
|
|
55
|
+
|
|
56
|
+
def _read_from_file(self):
|
|
57
|
+
with open(Path(self._path) / self.__class__.__name__, "r") as file:
|
|
58
|
+
return file.read()
|
|
59
|
+
|
|
60
|
+
def _read_from_memory(self):
|
|
61
|
+
return self._content
|
|
62
|
+
|
|
63
|
+
def __str__(self):
|
|
64
|
+
return self.read()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright © VASP Software GmbH,
|
|
2
|
+
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
3
|
+
from py4vasp._control import base
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class INCAR(base.InputFile):
|
|
7
|
+
"""The INCAR file defining the input parameters of a VASP calculation.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
path : str or Path
|
|
12
|
+
Defines where the INCAR file is stored. If set to None, the file will be kept
|
|
13
|
+
in memory.
|
|
14
|
+
"""
|