Source code for nanotools.atomcell

import numpy as np
import attr
from attr import field
from pint import Quantity
from nanotools.io.xyz import readxyz_pos
from nanotools.utils import to_quantity, ureg


def converter_frac_positions(x):
    if isinstance(x, str):
        # _, x = readxyz_pos(x)
        return x
    else:
        return np.array(x).reshape((-1, 3), order="C")


def converter_positions(x):
    shape = (-1, 3)
    return to_quantity(x, "angstrom", allow_none=True, allow_string=True, shape=shape)


def to_quantity_avec(x):
    shape = (3, 3)
    return to_quantity(x, "angstrom", allow_none=False, shape=shape)


[docs] @attr.s class AtomCell: """ ``AtomCell`` class. The ``AtomCell`` class contains information about the positions and types of the atoms, the lattice vectors defining the cell, and flags indicating the periodic boundary conditions. Attributes: fractional_positions (2D array, str): Fractional coordinates of each atom. May be an array of row-vectors or the path to an xyz formatted file. Examples:: atomcell.fractional_positions = [[0,0,0],[0.25,0.25,0.25]] atomcell.fractional_positions = "left_lead.xyz" positions (2D array, str, tuple): Cartesian coordinates of each atom. May be an array of row-vectors or the path to an xyz formatted file. By default, the units are in Angstroms. If a tuple is provided, the first element is the array of positions, the second element is the unit of the positions. Examples:: atomcell.positions = [ [1.16170546635838, 3.35586034069663, 16.96215117189000], [1.16170546635838, 0.67290534069663, 16.96711717189000], [3.48511646635838, 7.38012834069663, 16.96215117189000], [3.48511646635838, 4.69717334069663, 16.96711717189000] ] atomcell.positions = "graphene.xyz" atomcell.positions = ("ni_graphene.xyz", "bohr") avec (2D array): Three vectors defining a parallelepipedic domain. By default, the units are in Angstroms. If one wants to use another unit, it is necessary to use the pint library. Examples:: cell.avec = [[4.64682193271677, 0.0, 0.0], [0.0, 8.04853168139326, 0.0], [0.0, 0.0, 30.27076472739540]] from nanotools.utils import ureg cell.avec = [[4.64682193271677, 0.0, 0.0], [0.0, 8.04853168139326, 0.0], [0.0, 0.0, 30.27076472739540]] * ureg.bohr formula (str): Chemical formula of the system. If atomcell.positions is a path to an xyz file, the formula is automatically read. If atomcell.positions is an array, the formula must be specified. Examples:: atomcell.formula = "C2H4" pbc (list): List of three boolean values indicating the periodic boundary conditions in the x, y, and z directions, respectively. For two-probe system, pbc along the transport direction is automatically set to False. Only needed for the relaxation of general system. Examples:: atomcell.pbc = [False, True, False] """ fractional_positions: np.ndarray = attr.ib( default=None, converter=attr.converters.optional(converter_frac_positions), validator=attr.validators.optional( attr.validators.instance_of((np.ndarray, list, str)) ), ) positions: Quantity = field( default=None, converter=attr.converters.optional(converter_positions), validator=attr.validators.optional( attr.validators.instance_of((Quantity, tuple, str)) ), ) avec: Quantity = field( default=None, converter=to_quantity_avec, validator=attr.validators.optional(attr.validators.instance_of(Quantity)), ) formula: str = attr.ib( default=None, validator=attr.validators.optional(attr.validators.instance_of(str)), ) pbc = attr.ib( default=[True, True, True], ) def __attrs_post_init__(self): """This method is automatically called after the instance has been initialized. It checks if the positions or fractional_positions are specified, reads the positions and fractional positions, sets the lattice vectors (avec), and sets the periodic boundary conditions (pbc). """ if self.positions is None and self.fractional_positions is None: Exception("The keyword atomcell.positions must be specified.") self._read_positions() self._read_fractional_positions() self._set_avec(avec=self.avec) self._set_pbc() def _read_positions(self): if self.positions is not None: unit = None if isinstance(self.positions, tuple): self.positions, unit = self.positions[0], self.positions[1] if isinstance(self.positions, str): self.formula, self.positions = readxyz_pos(self.positions) if unit is None: self.positions = converter_positions(self.positions) else: self.positions *= getattr(ureg, unit) self.formula = compress_formula(self.formula) return def _read_fractional_positions(self): if self.fractional_positions is not None: if isinstance(self.fractional_positions, str): self.formula, self.fractional_positions = readxyz_pos( self.fractional_positions ) self.formula = compress_formula(self.formula) return def _set_avec(self, avec): self.avec = to_quantity_avec(avec) return def _set_pbc(self): if not isinstance(self.pbc, list) or len(self.pbc) != 3: raise ValueError( "The 'pbc' attribute must be a list of three boolean values." )
def compress_formula(formula): sform = split_formula(formula) cform = [] tmp0 = sform[0] count = 0 for s in sform: if s == tmp0: count += 1 else: if count > 1: cform.append(f"{tmp0}{count}") else: cform.append(f"{tmp0}") count = 1 tmp0 = s if count > 1: cform.append(f"{tmp0}{count}") else: cform.append(f"{tmp0}") return "".join(cform) def split_formula(formula): sform = [] i = 0 while i < len(formula): tmp = formula[i] i += 1 while i < len(formula) and not formula[i].isupper(): tmp += formula[i] i += 1 sform.append(tmp) return sform