Source code for aim2dat.strct.strct_validation

"""Validate structure input parameters."""

# Standard library imports
# import copy
import re
from typing import List, Tuple
import math

# Third party library imports
from scipy.spatial.distance import cdist
import numpy as np

# Internal library imports
from aim2dat.utils.element_properties import get_element_symbol


[docs] class SamePositionsError(ValueError): """Error raised when two or more atom sites share the same position.""" pass
def _structure_validate_elements(elements): if not isinstance(elements, (list, tuple, np.ndarray, str)): raise TypeError("`elements` must be a list, tuple, numpy array or str.") if isinstance(elements, str): elements = re.findall("[A-Z][^A-Z]*", elements) if len(elements) == 0: raise ValueError("`elements` must have a length greater than 0.") return tuple([get_element_symbol(el) for el in elements]) def _structure_validate_cell(cell: List[List[float]]): if isinstance(cell, (tuple, list, np.ndarray)): cell = np.array(cell).reshape((3, 3)) return tuple([tuple(v) for v in cell.tolist()]), tuple( [tuple(v) for v in np.linalg.inv(cell).tolist()] ) else: raise TypeError("`cell` must be a list or numpy array for periodic boundaries.") def _structure_validate_positions(positions, is_cartesian, cell, inv_cell, pbc): if not is_cartesian and cell is None: raise ValueError("`cell` must be set if `is_cartesian` is False.") positions_cart = [] positions_scaled = [] for position in positions: if len(position) != 3: raise ValueError("Length of one position must be 3.") if any([math.isnan(p) for p in position]): raise ValueError("`positions` must not contain 'nan' values.") if cell is None: positions_cart.append(tuple(float(p) for p in position)) else: if is_cartesian: positions_cart.append(tuple(float(p) for p in position)) positions_scaled.append( tuple(float(p) for p in np.transpose(inv_cell).dot(np.array(position))) ) else: positions_scaled.append(tuple(float(p) for p in position)) positions_cart.append( tuple(float(p) for p in np.transpose(cell).dot(np.array(position))) ) if cell is None: pos2check = positions_cart else: pos2check = [] for pos in positions_scaled: pos2check.append([round(pos[i], 15) % 1 if pbc[i] else pos[i] for i in range(3)]) dist_m = cdist(pos2check, pos2check) sites = set() for i in range(len(pos2check)): for j in range(i): if dist_m[i][j] < 1e-3: sites.add((i, j)) if sites: raise SamePositionsError( "Sites with the same position: " + ", ".join([f"{i}" for i in sites]) + "." ) if len(positions_scaled) == 0: return tuple(positions_cart), None else: return tuple(positions_cart), tuple(positions_scaled) def _structure_validate_el_pos( elements: List[str], positions: List[List[float]], pbc: List[bool], cell: List[List[float]], inv_cell: List[List[float]], is_cartesian: bool, ) -> Tuple[List[str], np.ndarray, np.ndarray]: if len(elements) != len(positions): raise ValueError("`elements` and `positions` must have the same length.") elements = _structure_validate_elements(elements) positions_cart, positions_scaled = _structure_validate_positions( positions, is_cartesian, cell, inv_cell, pbc ) if cell is None: positions_scaled = None return elements, positions_cart, positions_scaled