Source code for aim2dat.aiida_data.gaussian_cube_data
"""AiiDA data classes for gaussian cube files."""
# Standard library imports
import contextlib
import tempfile
import bz2
# Third party library imports
import numpy as np
from aiida.orm import Data
# Internal library imports
from aim2dat.utils.units import UnitConverter, length
[docs]
class GaussianCubeData(Data):
"""AiiDA data object to store gaussian cube files."""
_comp_file_name = "gcube.bz2"
def __init__(self, **kwargs):
"""Initialize object."""
super().__init__(**kwargs)
@property
def title(self):
"""list : Return the title of the cube file."""
return self.base.attributes.get("title", None)
@property
def comment(self):
"""list : Return the second line of the cube file."""
return self.base.attributes.get("comment", None)
@property
def origin(self):
"""list : Return the origin of the data."""
return self.base.attributes.get("origin", None)
@property
def cell(self):
"""list : Return the cell."""
return self.base.attributes.get("cell", None)
@property
def shape(self):
"""list : Return the number of points in each direction."""
return self.base.attributes.get("shape", None)
@property
def atomic_numbers(self):
"""list : Return the atomic numbers."""
return self.base.attributes.get("atomic_numbers", None)
@property
def atomic_charges(self):
"""list : Return the atomic charges."""
return self.base.attributes.get("atomic_charges", None)
@property
def atomic_positions(self):
"""list : Return the atomic positions (in bohr)."""
return self.base.attributes.get("atomic_positions", None)
@property
def dset_ids(self):
"""list : Return the data set identifiers."""
return self.base.attributes.get("dset_ids", None)
[docs]
@contextlib.contextmanager
def open_cube(self, path=None):
"""
Open cube file.
Parameters
----------
path : str or None
Path to the cube file.
Returns
-------
A file handle.
"""
if path is None:
path = self._comp_file_name
with self.base.repository.open(path, mode="rb") as handle:
with bz2.open(handle, "rt", compresslevel=9) as bz2_handle:
yield bz2_handle
[docs]
def get_content(self):
"""
Get content of the cube file.
Returns
-------
str
File conent as string.
"""
with self.open_cube() as handle:
return handle.read()
[docs]
def get_structure(self, unit="angstrom"):
"""
Get underlying structure.
Parameters
----------
unit : str
Length unit.
"""
if unit not in length.available_units:
raise ValueError(f"Unit '{unit}' is not supported.")
cell = [
[val * UnitConverter.convert_units(1.0, "bohr", unit) for val in cellv]
for cellv in self.cell
]
positions = [
[
val * UnitConverter.convert_units(1.0, "bohr", unit) - org
for val, org in zip(at, self.origin)
]
for at in self.atomic_positions
]
return {
"elements": self.atomic_numbers,
"positions": positions,
"cell": cell,
"is_cartesian": True,
}
[docs]
def get_cube_data(self):
"""
Get cube data points.
Returns
-------
np.array
Data of the cube file.
"""
data = np.zeros(
self.shape[0] * self.shape[1] * self.shape[2] * self.base.attributes.get("n_values")
)
counter = 0
with self.open_cube() as handle:
handle.seek(self.base.attributes.get("data_start"))
# Read data:
for line in handle:
line = line.split()
data[counter : counter + len(line)] = [float(val) for val in line]
counter += len(line)
if self.base.attributes.get("n_values") > 1:
data = data.reshape(self.shape + [self.base.attributes.get("n_values")])
else:
data = data.reshape(self.shape)
return data
[docs]
@classmethod
def set_from_file(cls, file_obj):
"""
Set information from existing cube file.
Returns
-------
GaussianCubeData
Data object containing the information from the file.
"""
gdata = GaussianCubeData()
# Read header:
gdata.base.attributes.set("title", file_obj.readline().strip())
gdata.base.attributes.set("comment", file_obj.readline().strip())
# Read origin:
line = file_obj.readline().split()
natoms = int(line[0])
gdata.base.attributes.set("origin", [float(val) for val in line[1:4]])
has_dset = False
if natoms < 0:
has_dset = True
natoms = abs(natoms)
n_val = 1
if len(line) == 5:
n_val = int(line[4])
gdata.base.attributes.set("n_values", n_val)
# Read cell:
cell = []
shape = []
for _ in range(3):
line = file_obj.readline().split()
shape.append(int(line[0]))
cell.append([float(val) * int(line[0]) for val in line[1:4]])
gdata.base.attributes.set("cell", cell)
gdata.base.attributes.set("shape", shape)
# Read atoms:
atomic_numbers = []
atomic_charges = []
atomic_positions = []
for _ in range(natoms):
line = file_obj.readline().split()
atomic_numbers.append(int(line[0]))
atomic_charges.append(float(line[1]))
atomic_positions.append([float(val) for val in line[2:5]])
gdata.base.attributes.set("atomic_numbers", atomic_numbers)
gdata.base.attributes.set("atomic_charges", atomic_charges)
gdata.base.attributes.set("atomic_positions", atomic_positions)
# Read dset-ids:
if has_dset:
dset_ids = []
line = file_obj.readline().split()
n_dset = int(line[0])
n_val *= n_dset
dset_ids += [int(val) for val in line[1:]]
while len(dset_ids) < n_dset:
line = file_obj.readline().split()
dset_ids += [int(val) for val in line[1:]]
gdata.base.attributes.set("dset_ids", dset_ids)
# Store file with bz2 compression:
gdata.base.attributes.set("data_start", file_obj.tell())
file_obj.seek(0)
with tempfile.NamedTemporaryFile() as handle:
with bz2.open(handle, "wt", compresslevel=9) as bz2_handle:
for line in file_obj:
bz2_handle.write(line)
handle.flush()
handle.seek(0)
gdata.base.repository.put_object_from_filelike(handle, gdata._comp_file_name)
return gdata