/
outputs.py
220 lines (172 loc) · 7.38 KB
/
outputs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
"""A representation of FHI-aims output (based on ASE output parser)."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
import numpy as np
from monty.json import MontyDecoder, MSONable
from pymatgen.io.aims.parsers import (
read_aims_header_info,
read_aims_header_info_from_content,
read_aims_output,
read_aims_output_from_content,
)
if TYPE_CHECKING:
from collections.abc import Sequence
from pathlib import Path
from emmet.core.math import Matrix3D, Vector3D
from pymatgen.core import Molecule, Structure
__author__ = "Andrey Sobolev and Thomas A. R. Purcell"
__version__ = "1.0"
__email__ = "andrey.n.sobolev@gmail.com and purcellt@arizona.edu"
__date__ = "November 2023"
class AimsOutput(MSONable):
"""The main output file for FHI-aims."""
def __init__(
self,
results: Molecule | Structure | Sequence[Molecule | Structure],
metadata: dict[str, Any],
structure_summary: dict[str, Any],
) -> None:
"""AimsOutput object constructor.
Args:
results (Molecule or Structure or Sequence[Molecule or Structure]): A list
of all images in an output file
metadata (Dict[str, Any]): The metadata of the executable used to perform
the calculation
structure_summary (Dict[str, Any]): The summary of the starting
atomic structure
"""
self._results = results
self._metadata = metadata
self._structure_summary = structure_summary
def as_dict(self) -> dict[str, Any]:
"""Create a dict representation of the outputs for MSONable."""
dct: dict[str, Any] = {"@module": type(self).__module__, "@class": type(self).__name__}
dct["results"] = self._results
dct["metadata"] = self._metadata
dct["structure_summary"] = self._structure_summary
return dct
@classmethod
def from_outfile(cls, outfile: str | Path) -> AimsOutput:
"""Construct an AimsOutput from an output file.
Args:
outfile: str | Path: The aims.out file to parse
Returns:
The AimsOutput object for the output file
"""
metadata, structure_summary = read_aims_header_info(outfile)
results = read_aims_output(outfile, index=slice(0, None))
return cls(results, metadata, structure_summary)
@classmethod
def from_str(cls, content: str) -> AimsOutput:
"""Construct an AimsOutput from an output file.
Args:
content (str): The content of the aims.out file
Returns:
The AimsOutput for the output file content
"""
metadata, structure_summary = read_aims_header_info_from_content(content)
results = read_aims_output_from_content(content, index=slice(0, None))
return cls(results, metadata, structure_summary)
@classmethod
def from_dict(cls, dct: dict[str, Any]) -> AimsOutput:
"""Construct an AimsOutput from a dictionary.
Args:
dct (dict[str, Any]): The dictionary used to create AimsOutput
Returns:
AimsOutput
"""
decoded = {k: MontyDecoder().process_decoded(v) for k, v in dct.items() if not k.startswith("@")}
for struct in decoded["results"]:
struct.properties = {k: MontyDecoder().process_decoded(v) for k, v in struct.properties.items()}
return cls(
decoded["results"],
decoded["metadata"],
decoded["structure_summary"],
)
def get_results_for_image(self, image_ind: int) -> Structure | Molecule:
"""Get the results dictionary for a particular image or slice of images.
Args:
image_ind (int): The index of the image to get the results for
Returns:
The results of the image with index images_ind
"""
return self._results[image_ind]
@property
def structure_summary(self) -> dict[str, Any]:
"""The summary of the material/molecule that the calculations represent."""
return self._structure_summary
@property
def metadata(self) -> dict[str, Any]:
"""The system metadata."""
return self._metadata
@property
def n_images(self) -> int:
"""The number of images in results."""
return len(self._results)
@property
def initial_structure(self) -> Structure | Molecule:
"""The initial structure for the calculations."""
return self._structure_summary["initial_structure"]
@property
def final_structure(self) -> Structure | Molecule:
"""The final structure for the calculation."""
return self._results[-1]
@property
def structures(self) -> Sequence[Structure | Molecule]:
"""All images in the output file."""
return self._results
@property
def fermi_energy(self) -> float:
"""The Fermi energy for the final structure in the calculation."""
return self.get_results_for_image(-1).properties["fermi_energy"]
@property
def vbm(self) -> float:
"""The HOMO level for the final structure in the calculation."""
return self.get_results_for_image(-1).properties["vbm"]
@property
def cbm(self) -> float:
"""The LUMO level for the final structure in the calculation."""
return self.get_results_for_image(-1).properties["cbm"]
@property
def band_gap(self) -> float:
"""The band gap for the final structure in the calculation."""
return self.get_results_for_image(-1).properties["gap"]
@property
def direct_band_gap(self) -> float:
"""The direct band gap for the final structure in the calculation."""
return self.get_results_for_image(-1).properties["direct_gap"]
@property
def final_energy(self) -> float:
"""The total energy for the final structure in the calculation."""
return self.get_results_for_image(-1).properties["energy"]
@property
def completed(self) -> bool:
"""Did the calculation complete."""
return len(self._results) > 0
@property
def aims_version(self) -> str:
"""The version of FHI-aims used for the calculation."""
return self._metadata["version_number"]
@property
def forces(self) -> Sequence[Vector3D] | None:
"""The forces for the final image of the calculation."""
force_array = self.get_results_for_image(-1).site_properties.get("force", None)
if isinstance(force_array, np.ndarray):
return force_array.tolist()
return force_array
@property
def stress(self) -> Matrix3D:
"""The stress for the final image of the calculation."""
return self.get_results_for_image(-1).properties.get("stress", None)
@property
def stresses(self) -> Sequence[Matrix3D] | None:
"""The atomic virial stresses for the final image of the calculation."""
stresses_array = self.get_results_for_image(-1).site_properties.get("atomic_virial_stress", None)
if isinstance(stresses_array, np.ndarray):
return stresses_array.tolist()
return stresses_array
@property
def all_forces(self) -> list[list[Vector3D]]:
"""The forces for all images in the calculation."""
all_forces_array = [res.site_properties.get("force", None) for res in self._results]
return [af.tolist() if isinstance(af, np.ndarray) else af for af in all_forces_array]