"""
Parsing various atomic structure files.
"""
import numpy
import numericalunits
from .generic import parse, cre_word, cre_nonspace, AbstractParser
from ..simple import band_structure, unit_cell
from ..types import UnitCell, Basis, Grid
from ..presentation import __elements_table__
from . import default_real_space_basis
[docs]class XSF(AbstractParser):
"""
Class for parsing `xcrysden <http://www.xcrysden.org>`_ files
commonly used in solid state visualisations.
Args:
data (str): string with the contents of the xsf file.
"""
@staticmethod
def valid_filename(name):
return name.lower().endswith(".xsf")
@staticmethod
def valid_header(header):
l = header.lower()
return "primvec" in l and "primcoord" in l
@unit_cell
[docs] def unitCells(self):
"""
Retrieves unit cells.
Returns:
A set of unit cells with atomic positions data.
"""
result = []
self.parser.reset()
while True:
mode = self.parser.closest(("primvec","primcoord"))
if mode == 0:
self.parser.skip("primvec")
self.parser.nextLine()
shape = self.parser.nextFloat((3,3))*numericalunits.angstrom
elif mode == 1:
self.parser.skip('primcoord')
self.parser.nextLine()
n = self.parser.nextInt()
coordinates = numpy.zeros((n,3))
values = numpy.zeros(n,dtype = 'S2')
for i in range(n):
self.parser.nextLine()
values[i] = self.parser.nextMatch(cre_word)
coordinates[i,:] = self.parser.nextFloat(3)*numericalunits.angstrom
result.append(UnitCell(
default_real_space_basis(shape),
coordinates,
values,
c_basis = 'cartesian'
))
else:
return result
[docs] def grids(self):
"""
Retrieves the grids.
Returns:
An array of cells with data on the grid. ( grid origin, grid vectors, data on the
grid, grid name, grid block name ).
"""
result = []
self.parser.reset()
while self.parser.present("begin_block_datagrid"):
self.parser.skip("begin_block_datagrid")
if self.parser.distance("_3d", default=-1) == 0:
mode = "3d"
elif self.parser.distance("_2d", default=-1) == 0:
mode = "2d"
else:
raise Exception("Failed to determine grid dimensions")
self.parser.nextLine()
block_name = self.parser.nextMatch(cre_nonspace)
while True:
self.parser.nextLine()
grid_name = self.parser.nextMatch(cre_nonspace)
expecting = "begin_datagrid_"+mode
expecting_2 = "datagrid_"+mode
if grid_name.lower() == "end_block_datagrid_"+mode:
break
elif grid_name.lower().startswith(expecting):
grid_name = grid_name[len(expecting)+1:]
elif grid_name.lower().startswith(expecting_2):
grid_name = grid_name[len(expecting_2)+1:]
else:
raise Exception("Failed to continue parsing grids at '{}'".format(grid_name))
if mode == "3d":
shape = self.parser.nextInt(3)
else:
shape = self.parser.nextInt(2)
origin = self.parser.nextFloat(3)*numericalunits.angstrom
if mode == "3d":
vectors = self.parser.nextFloat((3,3))*numericalunits.angstrom
else:
vectors = self.parser.nextFloat((2,3))*numericalunits.angstrom
data = self.parser.nextFloat(tuple(shape[::-1])).swapaxes(0,shape.size-1)[[slice(0,-1,1)]*vectors.shape[0]]
self.parser.skip("end_datagrid_"+mode)
meta = {
"xsf-grid-origin": origin,
"xsf-block-name": block_name,
"xsf-grid-name": grid_name,
}
c = Grid(
default_real_space_basis(vectors, meta = meta),
tuple(numpy.linspace(0,1,s, endpoint = False) for s in data.shape),
data,
)
result.append(c)
return result
[docs]class GaussianCube(AbstractParser):
"""
Class for parsing `Gaussian CUBE <http://www.gaussian.com/>`_ files.
Args:
data (str): string with the contents of the Gaussian CUBE file.
"""
@staticmethod
def valid_filename(name):
return name.lower().endswith(".cube")
[docs] def grid(self):
"""
Retrieves the grid.
Returns:
A grid.
"""
self.parser.reset()
self.parser.nextLine(2)
n = self.parser.nextInt()
if n<0:
origin = self.parser.nextFloat(3)*numericalunits.angstrom
else:
origin = self.parser.nextFloat(3)*numericalunits.aBohr
n = abs(n)
size = []
spacing = []
for i in range(3):
s = self.parser.nextInt()
size.append(s)
if s<0:
spacing.append(self.parser.nextFloat(3)*numericalunits.angstrom)
else:
spacing.append(self.parser.nextFloat(3)*numericalunits.aBohr)
spacing = numpy.array(spacing)
size = numpy.abs(size)
vectors = spacing*size[:,numpy.newaxis]
# Skip atomic coordinates
self.parser.nextLine(n+1)
data = self.parser.nextFloat(size)
return Grid(
default_real_space_basis(vectors),
tuple(numpy.linspace(0,1,s, endpoint = False) for s in data.shape),
data,
)
@unit_cell
[docs] def unitCell(self):
"""
Retrieves a unit cell.
Returns:
A unit cell with atomic positions data.
"""
result = []
self.parser.reset()
self.parser.nextLine(2)
# Number of atoms
n = self.parser.nextInt()
self.parser.nextLine()
# Lattice vectors
shape = []
for i in range(3):
nv = self.parser.nextInt()
v = self.parser.nextFloat(3)
if nv<0:
shape.append(v*abs(nv)*numericalunits.angstrom)
else:
shape.append(v*abs(nv)*numericalunits.aBohr)
c = []
v = []
for i in range(abs(n)):
aid = self.parser.nextInt()
self.parser.nextFloat()
ac = self.parser.nextFloat(3)
if not aid == 0:
v.append(__elements_table__[abs(aid)-1][0])
else:
v.append("??")
if nv<0:
c.append(ac*numericalunits.angstrom)
else:
c.append(ac*numericalunits.aBohr)
return UnitCell(default_real_space_basis(shape), c, v, c_basis="cartesian")
[docs]class XYZ(AbstractParser):
"""
Class for parsing XYZ structure files.
Args:
data (str): string with the contents of the XYZ file.
"""
vacuum_size = numericalunits.nm
@staticmethod
def valid_filename(name):
return name.lower().endswith(".xyz")
@unit_cell
[docs] def unitCell(self):
"""
Retrieves a unit cell.
Returns:
A unit cell with atomic positions data.
"""
result = []
self.parser.reset()
# Number of atoms
n = self.parser.nextInt()
self.parser.nextLine(2)
c = []
v = []
for i in range(abs(n)):
v.append(self.parser.nextMatch(cre_word))
c.append(self.parser.nextFloat(3)*numericalunits.angstrom)
c = numpy.array(c)
mx = c.max(axis = 0)
mn = c.min(axis = 0)
shape = mx-mn+XYZ.vacuum_size
return UnitCell(default_real_space_basis(shape, kind = 'orthorombic'), c, v, c_basis="cartesian")
# Lower case versions
xsf = XSF
cube = GaussianCube
xyz = XYZ