# RAD-tools - Sandbox (mainly condense matter plotting).
# Copyright (C) 2022-2024 Andrey Rybakov
#
# e-mail: anry@uv.es, web: rad-tools.org
#
# 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/>.
r"""
General 3D lattice.
"""
from typing import Iterable
import numpy as np
from radtools.geometry import absolute_to_relative
__all__ = ["Kpoints"]
[docs]
class Kpoints:
r"""
K-point path.
Parameters
----------
b1 : (3,) array_like
First reciprocal lattice vector :math:`\mathbf{b}_1`.
b2 : (3,) array_like
Second reciprocal lattice vector :math:`\mathbf{b}_2`.
b3 : (3,) array_like
Third reciprocal lattice vector :math:`\mathbf{b}_3`.
coordinates : list, optional
Coordinates are given in relative coordinates in reciprocal space.
names: list, optional
Names of the high symmetry points. Used for programming, not for plotting.
labels : list, optional
Dictionary of the high symmetry points labels for plotting.
Has to have the same length as ``coordinates``.
path : str, optional
K points path.
n : int
Number of points between each pair of the high symmetry points
(high symmetry points excluded).
Attributes
----------
b1 : (3,) :numpy:`ndarray`
First reciprocal lattice vector :math:`\mathbf{b}_1`.
b2 : (3,) :numpy:`ndarray`
Second reciprocal lattice vector :math:`\mathbf{b}_2`.
b3 : (3,) :numpy:`ndarray`
Third reciprocal lattice vector :math:`\mathbf{b}_3`.
hs_names : list
Names of the high symmetry points. Used for programming, not for plotting.
hs_coordinates : dict
Dictionary of the high symmetry points coordinates.
.. code-block:: python
{"name": [k_a, k_b, k_c], ... }
hs_labels : dict
Dictionary of the high symmetry points labels for plotting.
.. code-block:: python
{"name": "label", ... }
"""
def __init__(
self, b1, b2, b3, coordinates=None, names=None, labels=None, path=None, n=100
) -> None:
self.b1 = np.array(b1)
self.b2 = np.array(b2)
self.b3 = np.array(b3)
if coordinates is None:
coordinates = []
# Fill names and labels with defaults
if names is None:
names = [f"K{i+1}" for i in range(len(coordinates))]
if labels is None:
labels = [f"K$_{i+1}$" for i in range(len(coordinates))]
if labels is None:
labels = [name for name in names]
else:
if len(labels) != len(coordinates):
raise ValueError(
f"Amount of labels ({len(labels)}) does not match amount of points ({len(coordinates)})."
)
# Define high symmetry points attributes
self.hs_coordinates = dict(
[(names[i], np.array(coordinates[i])) for i in range(len(coordinates))]
)
self.hs_labels = dict([(names[i], labels[i]) for i in range(len(coordinates))])
self.hs_names = names
self._n = n
self._path = None
if path is None:
if len(self.hs_names) > 0:
path = [self.hs_names[0]]
else:
path = []
for point in self.hs_names[1:]:
path.append(f"-{point}")
path = "".join(path)
self.path = path
[docs]
def add_hs_point(self, name, coordinates, label, relative=True):
r"""
Add high symmetry point.
Parameters
----------
name : str
Name of the high symmetry point.
coordinates : (3,) array_like
Coordinates of the high symmetry point.
label : str
Label of the high symmetry point, ready to be plotted.
relative : bool, optional
Whether to interpret coordinates as relative or absolute.
"""
if name in self.hs_names:
raise ValueError(f"Point '{name}' already defined.")
if not relative:
coordinates = absolute_to_relative(
np.array([self.b1, self.b2, self.b3]), coordinates
)
self.hs_names.append(name)
self.hs_coordinates[name] = np.array(coordinates)
self.hs_labels[name] = label
@property
def path(self):
r"""
K points path.
Returns
-------
path : list of list of str
K points path. Each subpath is a list of the high symmetry points.
"""
return self._path
@path.setter
def path(self, new_path):
if isinstance(new_path, str):
tmp_path = new_path.split("|")
new_path = []
for i in range(len(tmp_path)):
subpath = tmp_path[i].split("-")
# Each subpath has to contain at least two points.
if len(subpath) != 1:
new_path.append(subpath)
elif isinstance(new_path, Iterable):
tmp_path = new_path
new_path = []
for subpath in tmp_path:
if isinstance(subpath, str) and "-" in subpath:
subpath = subpath.split("-")
# Each subpath has to contain at least two points.
if len(subpath) != 1:
new_path.append(subpath)
elif (
not isinstance(subpath, str)
and isinstance(subpath, Iterable)
and len(subpath) != 1
):
new_path.append(subpath)
else:
new_path = [tmp_path]
break
# Check if all points are defined.
for subpath in new_path:
for point in subpath:
if point not in self.hs_names:
message = f"Point '{point}' is not defined. Defined points are:"
for defined_name in self.hs_names:
message += (
f"\n {defined_name} : {self.hs_coordinates[defined_name]}"
)
raise ValueError(message)
self._path = new_path
@property
def path_string(self):
r"""
K points path as a string.
Returns
-------
path : str
"""
result = ""
for s_i, subpath in enumerate(self.path):
for i, name in enumerate(subpath):
if i != 0:
result += "-"
result += name
if s_i != len(self.path) - 1:
result += "|"
return result
@property
def n(self):
r"""
Amount of points between each pair of the high symmetry points
(high symmetry points excluded).
Returns
-------
n : int
"""
return self._n
@n.setter
def n(self, new_n):
if not isinstance(new_n, int):
raise ValueError(
f"n has to be integer. Given: {new_n}, type = {type(new_n)}"
)
self._n = new_n
@property
def labels(self):
r"""
Labels of high symmetry points, ready to be plotted.
For example for point "Gamma" it returns r"$\Gamma$".
If there are two high symmetry points following one another in the path,
it returns "X|Y" where X and Y are the labels of the two high symmetry points.
Returns
-------
labels : list of str
Labels, ready to be plotted. Same length as :py:attr:`.coordinates`.
"""
labels = []
for s_i, subpath in enumerate(self.path):
if s_i != 0:
labels[-1] += "|" + self.hs_labels[subpath[0]]
else:
labels.append(self.hs_labels[subpath[0]])
for name in subpath[1:]:
labels.append(self.hs_labels[name])
return labels
[docs]
def coordinates(self, relative=False):
r"""
Flatten coordinates of the high symmetry points, ready to be plotted.
Parameters
----------
relative : bool, optional
Whether to use relative coordinates instead of the absolute ones.
Returns
-------
coordinates : :numpy:`ndarray`
Coordinates, ready to be plotted. Same length as :py:attr:`.labels`.
"""
if relative:
cell = np.eye(3)
else:
cell = np.array([self.b1, self.b2, self.b3])
coordinates = []
for s_i, subpath in enumerate(self.path):
if s_i == 0:
coordinates.append(0)
for i, name in enumerate(subpath[1:]):
coordinates.append(
np.linalg.norm(
self.hs_coordinates[name] @ cell
- self.hs_coordinates[subpath[i]] @ cell
)
+ coordinates[-1]
)
return np.array(coordinates)
[docs]
def points(self, relative=False):
r"""
Coordinates of all points with n points between each pair of the high
symmetry points (high symmetry points excluded).
Parameters
----------
relative : bool, optional
Whether to use relative coordinates instead of the absolute ones.
Returns
-------
points : (N, 3) :numpy:`ndarray`
Coordinates of all points.
"""
if relative:
cell = np.eye(3)
else:
cell = np.array([self.b1, self.b2, self.b3])
points = None
for subpath in self.path:
for i in range(len(subpath) - 1):
name = subpath[i]
next_name = subpath[i + 1]
new_points = np.linspace(
self.hs_coordinates[name] @ cell,
self.hs_coordinates[next_name] @ cell,
self._n + 2,
)
if points is None:
points = new_points
else:
points = np.concatenate((points, new_points))
return points
# It can not just call for points and flatten them, because it has to treat "|" as a special case.
[docs]
def flatten_points(self, relative=False):
r"""
Flatten coordinates of all points with n points between each pair of the high
symmetry points (high symmetry points excluded). Used to plot band structure, dispersion, etc.
Parameters
----------
relative : bool, optional
Whether to use relative coordinates instead of the absolute ones.
Returns
-------
flatten_points : (N, 3) :numpy:`ndarray`
Flatten coordinates of all points.
"""
if relative:
cell = np.eye(3)
else:
cell = np.array([self.b1, self.b2, self.b3])
flatten_points = None
for s_i, subpath in enumerate(self.path):
for i in range(len(subpath) - 1):
name = subpath[i]
next_name = subpath[i + 1]
points = (
np.linspace(
self.hs_coordinates[name] @ cell,
self.hs_coordinates[next_name] @ cell,
self._n + 2,
)
- self.hs_coordinates[name] @ cell
)
delta = np.linalg.norm(points, axis=1)
if s_i == 0 and i == 0:
flatten_points = delta
else:
delta += flatten_points[-1]
flatten_points = np.concatenate((flatten_points, delta))
return flatten_points