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.
Files changed (84) hide show
  1. py4vasp/__init__.py +10 -0
  2. py4vasp/_analysis/mlff.py +325 -0
  3. py4vasp/_calculations.py +131 -0
  4. py4vasp/_combine/base.py +99 -0
  5. py4vasp/_combine/energies.py +9 -0
  6. py4vasp/_combine/forces.py +9 -0
  7. py4vasp/_combine/stresses.py +9 -0
  8. py4vasp/_config.py +4 -0
  9. py4vasp/_control/__init__.py +0 -0
  10. py4vasp/_control/base.py +64 -0
  11. py4vasp/_control/incar.py +14 -0
  12. py4vasp/_control/kpoints.py +14 -0
  13. py4vasp/_control/poscar.py +40 -0
  14. py4vasp/_raw/access.py +140 -0
  15. py4vasp/_raw/data.py +502 -0
  16. py4vasp/_raw/data_wrapper.py +101 -0
  17. py4vasp/_raw/definition.py +512 -0
  18. py4vasp/_raw/schema.py +202 -0
  19. py4vasp/_third_party/__init__.py +0 -0
  20. py4vasp/_third_party/graph/__init__.py +18 -0
  21. py4vasp/_third_party/graph/graph.py +242 -0
  22. py4vasp/_third_party/graph/mixin.py +107 -0
  23. py4vasp/_third_party/graph/plot.py +55 -0
  24. py4vasp/_third_party/graph/series.py +127 -0
  25. py4vasp/_third_party/interactive.py +34 -0
  26. py4vasp/_third_party/view/__init__.py +4 -0
  27. py4vasp/_third_party/view/mixin.py +40 -0
  28. py4vasp/_third_party/view/view.py +223 -0
  29. py4vasp/_util/check.py +25 -0
  30. py4vasp/_util/convert.py +81 -0
  31. py4vasp/_util/documentation.py +12 -0
  32. py4vasp/_util/import_.py +27 -0
  33. py4vasp/_util/index.py +373 -0
  34. py4vasp/_util/parser.py +323 -0
  35. py4vasp/_util/reader.py +26 -0
  36. py4vasp/_util/select.py +419 -0
  37. py4vasp/calculation/_CONTCAR.py +139 -0
  38. py4vasp/calculation/_OSZICAR.py +103 -0
  39. py4vasp/calculation/__init__.py +88 -0
  40. py4vasp/calculation/_band.py +197 -0
  41. py4vasp/calculation/_bandgap.py +283 -0
  42. py4vasp/calculation/_base.py +333 -0
  43. py4vasp/calculation/_born_effective_charge.py +52 -0
  44. py4vasp/calculation/_class.py +161 -0
  45. py4vasp/calculation/_density.py +350 -0
  46. py4vasp/calculation/_dielectric_function.py +177 -0
  47. py4vasp/calculation/_dielectric_tensor.py +76 -0
  48. py4vasp/calculation/_dispersion.py +143 -0
  49. py4vasp/calculation/_dos.py +158 -0
  50. py4vasp/calculation/_elastic_modulus.py +62 -0
  51. py4vasp/calculation/_energy.py +204 -0
  52. py4vasp/calculation/_fatband.py +58 -0
  53. py4vasp/calculation/_force.py +109 -0
  54. py4vasp/calculation/_force_constant.py +54 -0
  55. py4vasp/calculation/_internal_strain.py +59 -0
  56. py4vasp/calculation/_kpoint.py +281 -0
  57. py4vasp/calculation/_magnetism.py +317 -0
  58. py4vasp/calculation/_pair_correlation.py +121 -0
  59. py4vasp/calculation/_phonon.py +57 -0
  60. py4vasp/calculation/_phonon_band.py +92 -0
  61. py4vasp/calculation/_phonon_dos.py +97 -0
  62. py4vasp/calculation/_piezoelectric_tensor.py +75 -0
  63. py4vasp/calculation/_polarization.py +43 -0
  64. py4vasp/calculation/_potential.py +167 -0
  65. py4vasp/calculation/_projector.py +433 -0
  66. py4vasp/calculation/_selection.py +14 -0
  67. py4vasp/calculation/_slice.py +73 -0
  68. py4vasp/calculation/_stress.py +85 -0
  69. py4vasp/calculation/_structure.py +420 -0
  70. py4vasp/calculation/_system.py +17 -0
  71. py4vasp/calculation/_topology.py +185 -0
  72. py4vasp/calculation/_velocity.py +107 -0
  73. py4vasp/calculation/_workfunction.py +76 -0
  74. py4vasp/combine.py +5 -0
  75. py4vasp/control.py +11 -0
  76. py4vasp/exception.py +65 -0
  77. py4vasp/raw.py +20 -0
  78. py4vasp/scripts/__init__.py +0 -0
  79. py4vasp/scripts/error_analysis.py +145 -0
  80. py4vasp_core-0.9.0.dist-info/LICENSE.txt +178 -0
  81. py4vasp_core-0.9.0.dist-info/METADATA +109 -0
  82. py4vasp_core-0.9.0.dist-info/RECORD +84 -0
  83. py4vasp_core-0.9.0.dist-info/WHEEL +4 -0
  84. 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")
@@ -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
@@ -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)
@@ -0,0 +1,9 @@
1
+ # Copyright © VASP Software GmbH,
2
+ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3
+
4
+ from py4vasp._combine.base import BaseCombine
5
+
6
+
7
+ class Energies(BaseCombine):
8
+ def __init__(self):
9
+ pass
@@ -0,0 +1,9 @@
1
+ # Copyright © VASP Software GmbH,
2
+ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3
+
4
+ from py4vasp._combine.base import BaseCombine
5
+
6
+
7
+ class Forces(BaseCombine):
8
+ def __init__(self):
9
+ pass
@@ -0,0 +1,9 @@
1
+ # Copyright © VASP Software GmbH,
2
+ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3
+
4
+ from py4vasp._combine.base import BaseCombine
5
+
6
+
7
+ class Stresses(BaseCombine):
8
+ def __init__(self):
9
+ pass
py4vasp/_config.py ADDED
@@ -0,0 +1,4 @@
1
+ # Copyright © VASP Software GmbH,
2
+ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3
+ VASP_COLORS = ("#4C265F", "#2FB5AB", "#2C68FC", "#A82C35", "#808080")
4
+ VASP_PURPLE, VASP_CYAN, VASP_BLUE, VASP_RED, VASP_GRAY = VASP_COLORS
File without changes
@@ -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
+ """