Source code for wulfric.crystal._basic_manipulation

# ================================== LICENSE ===================================
# Wulfric - Cell, Atoms, K-path, visualization.
# Copyright (C) 2023 Andrey Rybakov
#
# e-mail: anry@uv.es, web: adrybakov.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# ================================ END LICENSE =================================
import numpy as np


__all__ = ["shift_atoms", "cure_negative", "ensure_000", "get_vector", "get_distance"]


[docs] def shift_atoms( atoms, gravity_point=(0.5, 0.5, 0.5), cell=None, gp_is_relative=True ) -> None: R""" Shifts all atoms with the same vector in a way that the ``gravity_point`` is located in the middle between minimum and maximum relative coordinates of the atoms, individually for each lattice vector. I.e. if there is one atom in the cell, then it is placed in the center of the cell for ``gravity_point`` = (0.5, 0.5, 0.5). Modifies given ``atoms`` dictionary. Parameters ---------- atoms : dict Dictionary with N atoms. Expected keys: * "positions" : (N, 3) |array-like|_ Positions of the atoms in the basis of lattice vectors (``cell``). In other words - relative coordinates of atoms. gravity_point : (3,) |array-like|_, default (0.5, 0.5, 0.5) Relative coordinates of the gravity point. cell : (3, 3) |array-like|_, optional Matrix of a cell, rows are interpreted as vectors. Required if ``gp_is_relative = False``. gp_is_relative : bool, default True Whether the ``gravity_point`` is given in relative coordinates. Examples -------- .. doctest:: >>> import wulfric >>> cell = [[2, 0, 0], [0, 2, 0], [0, 0, 2]] >>> atoms = { ... "names": ["Cr1", "Cr2"], ... "positions": [[0.0, 0.0, 0.0], [0.5, 0.5, 1.0]], ... } >>> wulfric.crystal.shift_atoms(atoms=atoms, gravity_point=(0.5, 0.5, 0.5)) >>> for i in range(len(atoms["names"])): ... print(atoms["names"][i], atoms["positions"][i]) Cr1 [0.25 0.25 0. ] Cr2 [0.75 0.75 1. ] >>> wulfric.crystal.shift_atoms( ... atoms, gravity_point=(1, 1, 1), cell=cell, gp_is_relative=False ... ) >>> for i in range(len(atoms["names"])): ... print(atoms["names"][i], atoms["positions"][i]) Cr1 [0.25 0.25 0. ] Cr2 [0.75 0.75 1. ] """ if not gp_is_relative: if cell is None: raise ValueError("cell is required if gp_is_relative False") # Transform from Cartesian coordinates to relative coordinates gravity_point = gravity_point @ np.linalg.inv(cell) min_coord = np.min(atoms["positions"], axis=0) max_coord = np.max(atoms["positions"], axis=0) shift = (max_coord + min_coord) / 2 atoms["positions"] = [ position - shift + gravity_point for position in atoms["positions"] ]
[docs] def cure_negative(atoms) -> None: R""" Shifts all atoms with the same vector in a way that all relative coordinates becomes non-negative. Modifies given ``atoms`` dictionary. Parameters ---------- atoms : dict Dictionary with N atoms. Expected keys: * "positions" : (N, 3) |array-like|_ Positions of the atoms in the basis of lattice vectors (``cell``). In other words - relative coordinates of atoms. Examples -------- .. doctest:: >>> import wulfric >>> atoms = { ... "names": ["Cr1", "Cr2"], ... "positions": [[-0.5, 0.5, 0.0], [0.1, 0.5, 0.0]], ... } >>> wulfric.crystal.cure_negative(atoms) >>> for i in range(len(atoms["names"])): ... print(atoms["names"][i], atoms["positions"][i]) Cr1 [0. 0.5 0. ] Cr2 [0.6 0.5 0. ] """ min_values = atoms["positions"][0] for position in atoms["positions"][1:]: min_values = np.minimum(min_values, position) shift = np.where(min_values < 0, -min_values, 0) atoms["positions"] = [position + shift for position in atoms["positions"]]
[docs] def ensure_000(atoms) -> None: r""" Ensures that all atoms are within (0,0,0) unit cell. In other word ensures that all relative coordinates of all atoms are :math:`\in [0,1]`. Parameters ---------- atoms : dict Dictionary with N atoms. Expected keys: * "positions" : (N, 3) |array-like|_ Positions of the atoms in the basis of lattice vectors (``cell``). In other words - relative coordinates of atoms. Examples -------- .. doctest:: >>> import wulfric >>> atoms = {"positions": [[0, 0.5, 0], [1.25, 0, -0.52], [0.25, -0.65, 2.375]]} >>> for p in atoms["positions"]: ... print(p) [0, 0.5, 0] [1.25, 0, -0.52] [0.25, -0.65, 2.375] >>> wulfric.crystal.ensure_000(atoms) >>> for p in atoms["positions"]: ... print(p) [0, 0.5, 0] [0.25, 0, 0.48] [0.25, 0.35, 0.375] """ for i in range(len(atoms["positions"])): for j in range(3): poscomp = atoms["positions"][i][j] # Ensure -1 < poscomp < 1 poscomp -= int(poscomp) # Ensure 0 <= poscomp <= 1 if poscomp < 0: poscomp += 1 atoms["positions"][i][j] = poscomp
[docs] def get_vector( cell, atoms, atom1, atom2, R=(0, 0, 0), return_relative=False ) -> np.ndarray: r""" Computes a vector from atom1 (from (0,0,0)) to atom2 (from (i,j,k)). Parameters ---------- cell : (3, 3) |array-like|_, Matrix of a cell, rows are interpreted as vectors. atoms : dict Dictionary with N atoms. Expected keys: * "positions" : (N, 3) |array-like|_ Positions of the atoms in the basis of lattice vectors (``cell``). In other words - relative coordinates of atoms. atom1 : int Index of the first atom in ``atoms["positions"]``. atom2 : int Index of the second atom in ``atoms["positions"]``. R : (3,) tuple of int, default (0, 0, 0) Radius vector of the unit cell for atom2 (i,j,k). return_relative : bool, default False Whether to return vector relative to the ``cell``. Returns ------- v : (3,) :numpy:`ndarray` Vector from atom1 in (0,0,0) cell to atom2 in R cell. Examples -------- .. doctest:: >>> import wulfric >>> cell = [[1, 0, 0], [0, 2, 0], [0, 0, 3]] >>> atoms = {"positions": [[0.5, 0, 0], [0, 0, 0.5]]} >>> wulfric.crystal.get_vector(cell, atoms, atom1=0, atom2=1, R=(0, 0, 0)) array([-0.5, 0. , 1.5]) >>> wulfric.crystal.get_vector(cell, atoms, atom1=0, atom2=1, R=(1, 0, 0)) array([0.5, 0. , 1.5]) >>> wulfric.crystal.get_vector(cell, atoms, atom1=0, atom2=1, R=(1, 0, -3)) array([ 0.5, 0. , -7.5]) >>> wulfric.crystal.get_vector( ... cell, atoms, atom1=0, atom2=1, R=(1, 0, -3), return_relative=True ... ) array([ 0.5, 0. , -2.5]) """ relative_vector = ( np.array(R, dtype=float) + atoms["positions"][atom2] - atoms["positions"][atom1] ) if return_relative: return relative_vector return relative_vector @ cell
[docs] def get_distance(cell, atoms, atom1, atom2, R=(0, 0, 0)) -> float: r""" Computes distance between atom1 (from (0,0,0)) and atom2 (from (i,j,k)). Parameters ---------- cell : (3, 3) |array-like|_, Matrix of a cell, rows are interpreted as vectors. atoms : dict Dictionary with N atoms. Expected keys: * "positions" : (N, 3) |array-like|_ Positions of the atoms in the basis of lattice vectors (``cell``). In other words - relative coordinates of atoms. atom1 : int Index of the first atom in ``atoms["positions"]``. atom2 : int Index of the second atom in ``atoms["positions"]``. R : (3,) tuple of int, default (0, 0, 0) Radius vector of the unit cell for atom2 (i,j,k). Returns ------- distance : float Distance between atom1 in (0,0,0) cell and atom2 in R cell. Examples -------- .. doctest:: >>> import wulfric >>> cell = [[1, 0, 0], [0, 2, 0], [0, 0, 3]] >>> atoms = {"positions": [[0.5, 0, 0], [0, 0, 0.5]]} >>> round( ... wulfric.crystal.get_distance( ... cell, atoms, atom1=0, atom2=1, R=(0, 0, 0) ... ), ... 8, ... ) 1.58113883 >>> round( ... wulfric.crystal.get_distance( ... cell, atoms, atom1=0, atom2=1, R=(1, 0, 0) ... ), ... 8, ... ) 1.58113883 >>> round( ... wulfric.crystal.get_distance( ... cell, atoms, atom1=0, atom2=1, R=(1, 0, -3) ... ), ... 8, ... ) 7.51664819 """ return float( np.linalg.norm( get_vector( cell=cell, atoms=atoms, atom1=atom1, atom2=atom2, R=R, return_relative=False, ) ) )