Skip to content

Commit 8ebe3b6

Browse files
authored
Feat: Add OSZICAR information to HDF5 (#136)
* Create raw and schema addition for OSZICAR * Implement hdf5 to py4vasp OSZICAR _data * Implement a to_graph method for the OSZICAR data
1 parent f885fa5 commit 8ebe3b6

File tree

7 files changed

+187
-1
lines changed

7 files changed

+187
-1
lines changed

src/py4vasp/_raw/data.py

+12
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,18 @@ class Magnetism:
312312
"Contains the orbital magnetization for all atoms"
313313

314314

315+
@dataclasses.dataclass
316+
class OSZICAR:
317+
"""The OSZICAR data as generated by VASP.
318+
319+
All data generated by VASP and traditionally stored in the OSZICAR file will be
320+
stored here. See https://www.vasp.at/wiki/index.php/OSZICAR for more details about
321+
what quantities to expect."""
322+
323+
convergence_data: VaspData
324+
"All columns of the OSZICAR file stored for all ionic steps."
325+
326+
315327
@dataclasses.dataclass
316328
class PairCorrelation:
317329
"""The pair-correlation function calculated during a MD simulation.

src/py4vasp/_raw/definition.py

+6
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,12 @@ def selections(quantity):
378378
orbital_moments="intermediate/ion_dynamics/magnetism/orbital_moments/values",
379379
)
380380
#
381+
schema.add(
382+
raw.OSZICAR,
383+
required=raw.Version(6, 5),
384+
convergence_data="intermediate/ion_dynamics/oszicar",
385+
)
386+
#
381387
group = "intermediate/pair_correlation"
382388
schema.add(
383389
raw.PairCorrelation,

src/py4vasp/_util/convert.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def to_complex(array):
2121

2222

2323
def quantity_name(quantity):
24-
if quantity == "CONTCAR":
24+
if quantity in ["CONTCAR", "OSZICAR"]:
2525
return quantity
2626
else:
2727
return _to_snakecase(quantity)

src/py4vasp/calculation/_OSZICAR.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright © VASP Software GmbH,
2+
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
4+
import numpy as np
5+
6+
from py4vasp import exception, raw
7+
from py4vasp._third_party import graph
8+
from py4vasp._util import convert
9+
from py4vasp.calculation import _base, _slice, _structure
10+
11+
INDEXING_OSZICAR = {
12+
"iteration_number": 0,
13+
"free_energy": 1,
14+
"free_energy_change": 2,
15+
"bandstructure_energy_change": 3,
16+
"number_hamiltonian_evaluations": 4,
17+
"norm_residual": 5,
18+
"difference_charge_density": 6,
19+
}
20+
21+
22+
class OSZICAR(_slice.Mixin, _base.Refinery, graph.Mixin):
23+
"""Access the convergence data for each electronic step.
24+
25+
The OSZICAR file written out by VASP stores information related to convergence.
26+
Please check the vasp-wiki (https://www.vasp.at/wiki/index.php/OSZICAR) for more
27+
details about the exact outputs generated for each combination of INCAR tags."""
28+
29+
@_base.data_access
30+
def to_dict(self, selection=None):
31+
"""Extract convergence data from the HDF5 file and make it available in a dict
32+
33+
Parameters
34+
----------
35+
selection: str
36+
Choose from either iteration_number, free_energy, free_energy_change,
37+
bandstructure_energy_change, number_hamiltonian_evaluations, norm_residual,
38+
difference_charge_density to get specific columns of the OSZICAR file. In
39+
case no selection is provided, supply all columns.
40+
41+
Returns
42+
-------
43+
dict
44+
Contains a dict from the HDF5 related to OSZICAR convergence data
45+
"""
46+
return_data = {}
47+
if selection is None:
48+
keys_to_include = INDEXING_OSZICAR
49+
else:
50+
if keys_to_include not in INDEXING_OSZICAR:
51+
message = """\
52+
Please choose a selection including at least one of the following keywords:
53+
iteration_number, free_energy, free_energy_change, bandstructure_energy_change,
54+
number_hamiltonian_evaluations, norm_residual, difference_charge_density. Else do not
55+
select anything and all OSZICAR outputs will be provided."""
56+
raise exception.RefinementError(message)
57+
keys_to_include = selection
58+
for key in INDEXING_OSZICAR:
59+
return_data[key] = self._read(key)
60+
return return_data
61+
62+
def _read(self, key):
63+
# data represents all of the electronic steps for all ionic steps
64+
data = getattr(self._raw_data, "convergence_data")
65+
iteration_number = data[:, 0]
66+
split_index = np.where(iteration_number == 1)[0]
67+
data = np.vsplit(data, split_index)[1:][self._steps]
68+
if isinstance(self._steps, slice):
69+
data = [raw.VaspData(_data) for _data in data]
70+
else:
71+
data = [raw.VaspData(data)]
72+
data_index = INDEXING_OSZICAR[key]
73+
return_data = [list(_data[:, data_index]) for _data in data]
74+
is_none = [_data.is_none() for _data in data]
75+
if len(return_data) == 1:
76+
return_data = return_data[0]
77+
return return_data if not np.all(is_none) else {}
78+
79+
def to_graph(self, selection="free_energy"):
80+
"""Graph the change in parameter with iteration number.
81+
82+
Parameters
83+
----------
84+
selection: str
85+
Choose from either iteration_number, free_energy, free_energy_change,
86+
bandstructure_energy_change, number_hamiltonian_evaluations, norm_residual,
87+
difference_charge_density to get specific columns of the OSZICAR file. In
88+
case no selection is provided, the free energy is plotted.
89+
90+
Returns
91+
-------
92+
Graph
93+
The Graph with the quantity plotted on y-axis and the iteration number of
94+
the x-axis.
95+
"""
96+
data = self.to_dict()
97+
series = graph.Series(data["iteration_number"], data[selection], selection)
98+
ylabel = " ".join(select.capitalize() for select in selection.split("_"))
99+
return graph.Graph(
100+
series=[series],
101+
xlabel="Iteration number",
102+
ylabel=ylabel,
103+
)

src/py4vasp/calculation/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class provides a more flexible interface with which you can determine the source
6060
"internal_strain",
6161
"kpoint",
6262
"magnetism",
63+
"OSZICAR",
6364
"pair_correlation",
6465
"phonon_band",
6566
"phonon_dos",

tests/calculation/test_oszicar.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright © VASP Software GmbH,
2+
# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
4+
import types
5+
6+
import pytest
7+
8+
from py4vasp import calculation
9+
10+
11+
@pytest.fixture
12+
def OSZICAR(raw_data):
13+
raw_oszicar = raw_data.OSZICAR()
14+
oszicar = calculation.OSZICAR.from_data(raw_oszicar)
15+
oszicar.ref = types.SimpleNamespace()
16+
convergence_data = raw_oszicar.convergence_data
17+
oszicar.ref.iteration_number = convergence_data[:, 0]
18+
oszicar.ref.free_energy = convergence_data[:, 1]
19+
oszicar.ref.free_energy_change = convergence_data[:, 2]
20+
oszicar.ref.bandstructure_energy_change = convergence_data[:, 3]
21+
oszicar.ref.number_hamiltonian_evaluations = convergence_data[:, 4]
22+
oszicar.ref.norm_residual = convergence_data[:, 5]
23+
oszicar.ref.difference_charge_density = convergence_data[:, 6]
24+
return oszicar
25+
26+
27+
def test_read(OSZICAR, Assert):
28+
actual = OSZICAR.read()
29+
expected = OSZICAR.ref
30+
Assert.allclose(actual["iteration_number"], expected.iteration_number)
31+
Assert.allclose(actual["free_energy"], expected.free_energy)
32+
Assert.allclose(actual["free_energy_change"], expected.free_energy_change)
33+
Assert.allclose(
34+
actual["bandstructure_energy_change"], expected.bandstructure_energy_change
35+
)
36+
Assert.allclose(
37+
actual["number_hamiltonian_evaluations"],
38+
expected.number_hamiltonian_evaluations,
39+
)
40+
Assert.allclose(actual["norm_residual"], expected.norm_residual)
41+
Assert.allclose(
42+
actual["difference_charge_density"], expected.difference_charge_density
43+
)
44+
45+
46+
def test_plot(OSZICAR, Assert):
47+
graph = OSZICAR.plot()
48+
assert graph.xlabel == "Iteration number"
49+
assert graph.ylabel == "Free Energy"
50+
assert len(graph.series) == 1
51+
Assert.allclose(graph.series[0].x, OSZICAR.ref.iteration_number)
52+
Assert.allclose(graph.series[0].y, OSZICAR.ref.free_energy)

tests/conftest.py

+12
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ def CONTCAR(selection):
123123
else:
124124
raise exception.NotImplemented()
125125

126+
@staticmethod
127+
def OSZICAR(selection=None):
128+
return _example_OSZICAR()
129+
126130
@staticmethod
127131
def density(selection):
128132
parts = selection.split()
@@ -649,6 +653,14 @@ def _Sr2TiO4_cell():
649653
)
650654

651655

656+
def _example_OSZICAR():
657+
random_convergence_data = np.random.rand(9, 6)
658+
iteration_number = np.arange(1, 10)[:, np.newaxis]
659+
convergence_data = np.hstack([iteration_number, random_convergence_data])
660+
convergence_data = raw.VaspData(convergence_data)
661+
return raw.OSZICAR(convergence_data=convergence_data)
662+
663+
652664
def _Sr2TiO4_CONTCAR():
653665
structure = _Sr2TiO4_structure()
654666
structure.cell.lattice_vectors = structure.cell.lattice_vectors[-1]

0 commit comments

Comments
 (0)