Source code for hiphive.core.structures

import numpy as np
from ase import Atoms


[docs] def site_offset_to_spos( site: int, offset: np.ndarray, basis_spos: list[np.ndarray], ) -> np.ndarray: """ Returns the scaled position of an atom at the specified site and offset relative to the basis in scaled coordinates. Parameters ---------- site Site index. offset Offset in scaled coordinates (should be integers). basis_spos Positions of atoms in basis in scaled coordinates. """ return offset + basis_spos[site]
[docs] def spos_to_pos(spos: np.ndarray, cell: np.ndarray) -> np.ndarray: """ Returns the Cartesian coordinate given the scaled coordinate and cell metric (cell vectors as rows). Parameters ---------- spos Position in scaled coordinates. cell Cell metric. """ return np.dot(spos, cell)
[docs] def pos_to_spos(pos: np.ndarray, cell: np.ndarray) -> np.ndarray: """Returns the scaled coordinate given the Cartesian coordinate and the cell metric. Inverse of :func:`spos_to_pos`. Parameters ---------- pos Position in Cartesian coordinates. cell Cell metric. """ return np.linalg.solve(cell.T, pos)
[docs] def spos_to_site_offset( spos: np.ndarray, basis_spos: list[np.ndarray], symprec: float, ) -> np.ndarray: """ Returns the site and offset of the atom at the specified scaled coordinate given the scaled positions of the basis atoms. Parameters ---------- spos Position in scaled coordinates. basis_spos Positions of atoms in basis in scaled coordinates. symprec Tolerance imposed when rounding the offset. """ for site, sp in enumerate(basis_spos): offset = spos - sp rounded_offset = offset.round(0).astype(np.int64) # TODO: fix tolerance (symprec should be w.r.t. cart. coord.) if np.allclose(rounded_offset, offset, rtol=0, atol=symprec): return site, rounded_offset raise Exception('spos {} not compatible with basis {} using symprec {}' .format(spos, basis_spos, symprec))
[docs] def pos_to_site_offset( pos: np.ndarray, cell: np.ndarray, basis_spos: list[np.ndarray], symprec: float, ) -> np.ndarray: """Returns offset given the position in Cartesian coordinates and the cell metric. Parameters ---------- pos Position in Cartesian coordinates. cell Cell metric. basis_spos Positions of atoms in basis in scaled coordinates. symprec Tolerance imposed when rounding the offset. """ spos = pos_to_spos(pos, cell) return spos_to_site_offset(spos, basis_spos, symprec)
[docs] def site_offset_to_pos( site: int, offset: np.ndarray, cell: np.ndarray, basis_spos: list[np.ndarray], ) -> np.ndarray: """Returns the position in Cartesian coordinates given a site index, an offset, the cell metric, and the position in the basis. Parameters ---------- site Site index. offset Offset in scaled coordinates (should be integers). cell Cell metric. basis_spos Positions of atoms in basis in scaled coordinates. """ spos = site_offset_to_spos(site, offset, basis_spos) return spos_to_pos(spos, cell)
[docs] class BaseAtom: """ This class represents an atom placed in an infinite crystal. Attributes ---------- site : int Site index. offset : list[int] Offset in scaled coordinates. """ def __init__(self, site, offset): assert type(site) is int, type(site) assert len(offset) == 3, len(offset) assert (all(type(i) is int for i in offset) or all(type(i) is np.int64 for i in offset)), type(offset[0]) self._site = site self._offset = np.array(offset) @property def site(self) -> int: """ Site index. """ return self._site @property def offset(self) -> np.ndarray: """ Offset in scaled coordinates. """ return self._offset
[docs] def astype(self, dtype): """ Useful arguments: list, tuple, np.int64""" return dtype((self._site, *self._offset))
[docs] class Atom(BaseAtom): """ This class represents a crystal atom in a given structure. """ def __init__(self, *args, **kwargs): self._structure = kwargs.pop('structure', None) super().__init__(*args, **kwargs) @property def pos(self) -> np.ndarray: return site_offset_to_pos(self._site, self._offset, self._structure.cell, self._structure.spos) @property def number(self) -> int: return self._structure.numbers[self._site]
[docs] class SupercellAtom(Atom): """ Represents an atom in a supercell but site and offset given by an underlying primitve cell.""" def __init__(self, *args, **kwargs): self._index = kwargs.pop('index') assert type(self._index) is int super().__init__(*args, **kwargs) @property def index(self): return self._index
[docs] class Structure: """ This class essentially wraps the ASE :class:`Atoms` class but is a bit more careful with respect to periodic boundary conditions and scaled coordinates. Note that it returns :class:`hiphive.Atom` objects when queried for individual atoms. Parameters ---------- atoms Atomic structure. symprec Tolerance imposed when rounding scaled coordinates. """ def __init__(self, atoms: Atoms, symprec: float = 1e-6): spos = atoms.get_scaled_positions(wrap=False) for sp in spos.flat: if not (-symprec < sp < (1 - symprec)): raise ValueError('bad spos {}'.format(sp)) self._spos = spos self._cell = atoms.cell self._numbers = atoms.numbers def __len__(self): return len(self._spos) @property def spos(self) -> np.ndarray: """ Scaled coordinates. """ return self._spos @property def cell(self): """ Cell metric. """ return self._cell def __getitem__(self, index): if index >= len(self): raise IndexError('Structure contains {} atoms'.format(len(self))) return Atom(index, (0, 0, 0), structure=self)
[docs] def atom_from_pos(self, pos: np.ndarray, symprec: float = None) -> Atom: """ Returns atom given a position in Cartesian coordinates. Parameters ---------- pos Position in Cartesian coordinates. symprec Tolerance imposed when rounding scaled coordinates. """ if symprec is None: symprec = self._symprec site, offset = pos_to_site_offset(pos, self._cell, self._spos, symprec) return Atom(site, offset, structure=self)
[docs] class Supercell: """ This class tries to represent atoms in a supercell as positioned on the primitive lattice. Parameters ---------- supercell Supercell structure. prim Primitive structure. symprec Tolerance imposed when rounding scaled coordinates. """ def __init__(self, supercell: Atoms, prim: Atoms, symprec: float): self._supercell = Structure(supercell) self._prim = Structure(prim) self._symprec = symprec self._map = list() self._inverse_map_lookup = dict() self._create_map() def _create_map(self): for atom in self._supercell: atom = self._prim.atom_from_pos(atom.pos, self._symprec) self._map.append(atom.astype(tuple)) def wrap_atom(self, atom): atom = Atom(atom.site, atom.offset, structure=self._prim) tup = atom.astype(tuple) index = self._inverse_map_lookup.get(tup, None) if index is None: atom = self._supercell.atom_from_pos(atom.pos, self._symprec) index = atom.site self._inverse_map_lookup[tup] = index return self[index]
[docs] def index(self, site: int, offset: np.ndarray) -> int: """" Returns index of atom given a site index and an offset. Parameters ---------- site Site index. offset Offset in scaled coordinates (should be integers). """ atom = self.wrap_atom(BaseAtom(site, offset)) return atom.index
def __getitem__(self, index): tup = self._map[index] return SupercellAtom(tup[0], tup[1:], structure=self._prim, index=index) def __len__(self): return len(self._supercell)