r"""
Bravais lattices.
"""
from math import cos, floor, log10, pi, sin, sqrt, tan
import numpy as np
from radtools.crystal.identify import lepage
from radtools.crystal.lattice import Lattice
from radtools.routines import param_from_cell, toradians, volume
__all__ = [
"CUB",
"FCC",
"BCC",
"TET",
"BCT",
"ORC",
"ORCF",
"ORCI",
"ORCC",
"HEX",
"RHL",
"MCL",
"MCLC",
"TRI",
"lattice_example",
"bravais_lattice_from_param",
"bravais_lattice_from_cell",
]
class NotEnoughParameters(Exception):
r"""
Raised if one tries to create a Bravais lattice without enough parameters.
Gives a summary of required parameters for the Bravais lattice type.
Parameters
----------
lattice : str
Lattice type name.
parameters : dict
Dictionary of parameters names and values
"""
def __init__(self, lattice, parameters):
self.message = (
f"\nFor the Bravais lattice type '{lattice}' the cell "
+ f"\nor the following lattice parameters are needed:\n "
)
for i, param in enumerate(parameters):
self.message += f"{param[0]}"
if i != len(parameters) - 1:
self.message += ", "
self.message += "\nGot:\n"
for i, param in enumerate(parameters):
self.message += f" {param[0]} = {param[1]}"
if i != len(parameters) - 1:
self.message += ", "
def __str__(self):
return self.message
class CellTypeMismatch(Exception):
r"""
Raised when one tries to create a Bravais lattice with a cell (or set of parameters)
which does not match desired Bravais lattice type.
Parameters
----------
lattice_type : str
Target lattice type
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
a : float
Length of the :math:`a_1` vector.
b : float
Length of the :math:`a_2` vector.
c : float
Length of the :math:`a_3` vector.
alpha : float
Angle between vectors :math:`a_2` and :math:`a_3`. In degrees.
beta : float
Angle between vectors :math:`a_1` and :math:`a_3`. In degrees.
gamma : float
Angle between vectors :math:`a_1` and :math:`a_2`. In degrees.
correct_lattice_type : str, optional
Correct lattice type
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
def __init__(
self,
lattice_type,
eps_rel,
a,
b,
c,
alpha,
beta,
gamma,
correct_lattice_type=None,
):
n = abs(
floor(
log10(abs(eps_rel * volume(a, b, c, alpha, beta, gamma) ** (1 / 3.0)))
)
)
self.message = (
f"\nCell is not '{lattice_type}':\n"
+ f" Relative epsilon = {eps_rel},\n"
+ f" a = {a:{n+6}.{n+1}f},\n"
+ f" b = {a:{n+6}.{n+1}f},\n"
+ f" c = {c:{n+6}.{n+1}f},\n"
+ f" alpha = {alpha:{n+6}.{n+1}f},\n"
+ f" beta = {beta:{n+6}.{n+1}f},\n"
+ f" gamma = {gamma:{n+6}.{n+1}f},\n"
)
if correct_lattice_type is None:
correct_lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
self.message += (
f"Lattice type defined from parameters: '{correct_lattice_type}'"
)
def __str__(self):
return self.message
# 1
[docs]
class CUB(Lattice):
r"""
Cubic (CUB, cP)
Primitive and conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, a, 0)
\boldsymbol{a}_3 = (0, 0, a)
.. seealso::
:ref:`lattice-cub` for more information.
Parameters
----------
a : float, optional
Length of the lattice vectors of the conventional cel.
cell : (3,3) |array_like|_, optional
Cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vectors of the conventional cel.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "cP"
def __init__(self, a: float = None, cell=None, eps_rel=1e-5) -> None:
if a is None and cell is None:
raise NotEnoughParameters("CUB", [("a", a)])
if cell is not None:
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "CUB":
raise CellTypeMismatch(
"CUB", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
super().__init__(cell)
self.conv_a = (a + b + c) / 3
else:
super().__init__([a, 0, 0], [0, a, 0], [0, 0, a])
self.conv_a = a
self.conv_cell = self.cell
self.kpoints = {
"G": np.array([0, 0, 0]),
"M": np.array([1 / 2, 1 / 2, 0]),
"R": np.array([1 / 2, 1 / 2, 1 / 2]),
"X": np.array([0, 1 / 2, 0]),
}
self._default_path = [["G", "X", "M", "G", "R", "X"], ["M", "R"]]
# 2
[docs]
class FCC(Lattice):
r"""
Face-centred cubic (FCC, cF)
Conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, a, 0)
\boldsymbol{a}_3 = (0, 0, a)
Primitive lattice:
.. math::
\boldsymbol{a}_1 = (0, a/2, a/2)
\boldsymbol{a}_2 = (a/2, 0, a/2)
\boldsymbol{a}_3 = (a/2, a/2, 0)
.. seealso::
:ref:`lattice-fcc` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional cel.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional cel.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "cF"
def __init__(self, a: float = None, cell=None, eps_rel=1e-5) -> None:
if a is None and cell is None:
raise NotEnoughParameters("FCC", [("a", a)])
if cell is not None:
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "FCC":
raise CellTypeMismatch(
"FCC", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
super().__init__(cell)
self.conv_cell = [
[-1.0, 1.0, 1.0],
[1.0, -1.0, 1.0],
[1.0, 1.0, -1.0],
] @ self.cell
a, b, c, alpha, beta, gamma = param_from_cell(self.conv_cell)
self.conv_a = (a + b + c) / 3
else:
self.conv_a = a
super().__init__([0, a / 2, a / 2], [a / 2, 0, a / 2], [a / 2, a / 2, 0])
self.conv_cell = np.diag([self.conv_a, self.conv_a, self.conv_a])
self.kpoints = {
"G": np.array([0, 0, 0]),
"K": np.array([3 / 8, 3 / 8, 3 / 4]),
"L": np.array([1 / 2, 1 / 2, 1 / 2]),
"U": np.array([5 / 8, 1 / 4, 5 / 8]),
"W": np.array([1 / 2, 1 / 4, 3 / 4]),
"X": np.array([1 / 2, 0, 1 / 2]),
}
self._default_path = [
["G", "X", "W", "K", "G", "L", "U", "W", "L", "K"],
["U", "X"],
]
# 3
[docs]
class BCC(Lattice):
r"""
Body-centered cubic (BCC, cI)
Conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, a, 0)
\boldsymbol{a}_3 = (0, 0, a)
Primitive lattice:
.. math::
\boldsymbol{a}_1 = (-a/2, a/2, a/2)
\boldsymbol{a}_2 = (a/2, -a/2, a/2)
\boldsymbol{a}_3 = (a/2, a/2, -a/2)
.. seealso::
:ref:`lattice-bcc` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "cI"
def __init__(self, a: float = None, cell=None, eps_rel=1e-5) -> None:
if a is None and cell is None:
raise NotEnoughParameters("BCC", [("a", a)])
if cell is not None:
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "BCC":
raise CellTypeMismatch(
"BCC", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
super().__init__(cell)
self.conv_cell = [
[0, 1.0, 1.0],
[1.0, 0, 1.0],
[1.0, 1.0, 0],
] @ self.cell
a, b, c, alpha, beta, gamma = param_from_cell(self.conv_cell)
self.conv_a = (a + b + c) / 3
else:
self.conv_a = a
super().__init__(
[-a / 2, a / 2, a / 2], [a / 2, -a / 2, a / 2], [a / 2, a / 2, -a / 2]
)
self.conv_cell = np.diag([self.conv_a, self.conv_a, self.conv_a])
self.kpoints = {
"G": np.array([0, 0, 0]),
"H": np.array([1 / 2, -1 / 2, 1 / 2]),
"P": np.array([1 / 4, 1 / 4, 1 / 4]),
"N": np.array([0, 0, 1 / 2]),
}
self._default_path = [["G", "H", "N", "G", "P", "H"], ["P", "N"]]
# 4
[docs]
class TET(Lattice):
r"""
Tetragonal (TET, tP)
Primitive and conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, a, 0)
\boldsymbol{a}_3 = (0, 0, c)
.. seealso::
:ref:`lattice-tet` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional lattice.
c : float, optional
Length of the lattice vector of the conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_c : float
Length of the lattice vector of the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "tP"
def __init__(
self, a: float = None, c: float = None, cell=None, eps_rel=1e-5
) -> None:
if (a is None or c is None) and cell is None:
raise NotEnoughParameters("TET", [("a", a), ("c", c)])
if cell is not None:
eps = eps_rel * volume(cell) ** (1 / 3.0)
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "TET":
raise CellTypeMismatch(
"TET", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
if not (a < c - eps or c < a - eps):
cell = [cell[0], cell[2], cell[1]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
elif not (b < c - eps or b < c - eps):
cell = [cell[1], cell[2], cell[0]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
super().__init__(cell)
self.conv_a = (a + b) / 2
self.conv_c = c
else:
lattice_type = lepage(a, a, c, 90, 90, 90, eps_rel=eps_rel)
if lattice_type != "TET":
raise CellTypeMismatch(
"TET", eps_rel, a, a, c, 90, 90, 90, lattice_type
)
self.conv_a = a
self.conv_c = c
super().__init__([a, 0, 0], [0, a, 0], [0, 0, c])
self.conv_cell = self.cell
self.kpoints = {
"G": np.array([0, 0, 0]),
"A": np.array([1 / 2, 1 / 2, 1 / 2]),
"M": np.array([1 / 2, 1 / 2, 0]),
"R": np.array([0, 1 / 2, 1 / 2]),
"X": np.array([0, 1 / 2, 0]),
"Z": np.array([0, 0, 1 / 2]),
}
self._default_path = [
["G", "X", "M", "G", "Z", "R", "A", "Z"],
["X", "R"],
["M", "A"],
]
# 5
[docs]
class BCT(Lattice):
r"""
Body-centred tetragonal (BCT, tI)
Conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, a, 0)
\boldsymbol{a}_3 = (0, 0, c)
Primitive lattice:
.. math::
\boldsymbol{a}_1 = (-a/2, a/2, c/2)
\boldsymbol{a}_2 = (a/2, -a/2, c/2)
\boldsymbol{a}_3 = (a/2, a/2, -c/2)
.. seealso::
:ref:`lattice-bct` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of conventional lattice.
c : float, optional
Length of the lattice vector of conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of conventional lattice.
conv_c : float
Length of the lattice vector of conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "tI"
def __init__(
self, a: float = None, c: float = None, cell=None, eps_rel=1e-5
) -> None:
if (a is None or c is None) and cell is None:
raise NotEnoughParameters("BCT", [("a", a), ("c", c)])
if cell is not None:
eps = eps_rel * volume(cell) ** (1 / 3.0)
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "BCT":
raise CellTypeMismatch(
"BCT", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
conv_cell = [[0, 1.0, 1.0], [1.0, 0, 1.0], [1.0, 1.0, 0]] @ cell
a, b, c, alpha, beta, gamma = param_from_cell(conv_cell)
if not (a < c - eps or c < a - eps):
cell = [cell[0], cell[2], cell[1]]
elif not (b < c - eps or b < c - eps):
cell = [cell[1], cell[2], cell[0]]
self.conv_cell = [[0, 1.0, 1.0], [1.0, 0, 1.0], [1.0, 1.0, 0]] @ cell
a, b, c, alpha, beta, gamma = param_from_cell(self.conv_cell)
super().__init__(cell)
self.conv_a = (a + b) / 2
self.conv_c = c
else:
self.conv_a = a
self.conv_c = c
super().__init__(
[-a / 2, a / 2, c / 2], [a / 2, -a / 2, c / 2], [a / 2, a / 2, -c / 2]
)
a, b, c, alpha, beta, gamma = param_from_cell(self.cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "BCT":
raise CellTypeMismatch(
"BCT", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
self.conv_cell = np.diag([self.conv_a, self.conv_a, self.conv_c])
self._PLOT_NAMES["S"] = "$\\Sigma$"
self._PLOT_NAMES["S1"] = "$\\Sigma_1$"
if self.variation == "BCT1":
eta = (1 + self.conv_c**2 / self.conv_a**2) / 4
self.kpoints = {
"G": np.array([0, 0, 0]),
"M": np.array([-1 / 2, 1 / 2, 1 / 2]),
"N": np.array([0, 1 / 2, 0]),
"P": np.array([1 / 4, 1 / 4, 1 / 4]),
"X": np.array([0, 0, 1 / 2]),
"Z": np.array([eta, eta, -eta]),
"Z1": np.array([-eta, 1 - eta, eta]),
}
self._default_path = [
["G", "X", "M", "G", "Z", "P", "N", "Z1", "M"],
["X", "P"],
]
elif self.variation == "BCT2":
eta = (1 + self.conv_a**2 / self.conv_c**2) / 4
zeta = self.conv_a**2 / (2 * self.conv_c**2)
self.kpoints = {
"G": np.array([0, 0, 0]),
"N": np.array([0, 1 / 2, 0]),
"P": np.array([1 / 4, 1 / 4, 1 / 4]),
"S": np.array([-eta, eta, eta]),
"S1": np.array([eta, 1 - eta, -eta]),
"X": np.array([0, 0, 1 / 2]),
"Y": np.array([-zeta, zeta, 1 / 2]),
"Y1": np.array([1 / 2, 1 / 2, -zeta]),
"Z": np.array([1 / 2, 1 / 2, -1 / 2]),
}
self._default_path = [
[
"G",
"X",
"Y",
"S",
"G",
"Z",
"S1",
"N",
"P",
"Y1",
"Z",
],
["X", "P"],
]
@property
def variation(self):
r"""
Two variations of the Lattice.
:math:`\text{BCT}_1: c < a` and :math:`\text{BCT}_2: c > a`
Returns
-------
variation : str
Variation of the lattice. "BCT1" or "BCT2".
"""
if self.conv_a > self.conv_c:
return "BCT1"
elif self.conv_a < self.conv_c:
return "BCT2"
# 6
[docs]
class ORC(Lattice):
r"""
Orthorhombic (ORC, oP)
:math:`a < b < c`
Primitive and conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, b, 0)
\boldsymbol{a}_3 = (0, 0, c)
.. seealso::
:ref:`lattice-orc` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional lattice.
b : float, optional
Length of the lattice vector of the conventional lattice.
c : float, optional
Length of the lattice vector of the conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_b : float
Length of the lattice vector of the conventional lattice.
conv_c : float
Length of the lattice vector of the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "oP"
def __init__(
self, a: float = None, b: float = None, c: float = None, cell=None, eps_rel=1e-5
) -> None:
if (a is None or b is None or c is None) and cell is None:
raise NotEnoughParameters("ORC", [("a", a), ("b", b), ("c", c)])
if cell is not None:
eps = eps_rel * volume(cell) ** (1 / 3.0)
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "ORC":
raise CellTypeMismatch(
"ORC", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
if b < a - eps:
# minus preserves right-hand order
cell = [cell[1], cell[0], -cell[2]]
if c < a - eps:
cell = [cell[2], cell[0], cell[1]]
elif c < b - eps:
# minus preserves right-hand order
cell = [cell[0], cell[2], -cell[1]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
super().__init__(cell)
self.conv_a = a
self.conv_b = b
self.conv_c = c
else:
lattice_type = lepage(a, b, c, 90, 90, 90, eps_rel=eps_rel)
if lattice_type != "ORC":
raise CellTypeMismatch(
"ORC", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
a, b, c = tuple(sorted([a, b, c]))
self.conv_a = a
self.conv_b = b
self.conv_c = c
super().__init__([a, 0, 0], [0, b, 0], [0, 0, c])
self.conv_cell = self.cell
self.kpoints = {
"G": np.array([0, 0, 0]),
"R": np.array([1 / 2, 1 / 2, 1 / 2]),
"S": np.array([1 / 2, 1 / 2, 0]),
"T": np.array([0, 1 / 2, 1 / 2]),
"U": np.array([1 / 2, 0, 1 / 2]),
"X": np.array([1 / 2, 0, 0]),
"Y": np.array([0, 1 / 2, 0]),
"Z": np.array([0, 0, 1 / 2]),
}
self._default_path = [
["G", "X", "S", "Y", "G", "Z", "U", "R", "T", "Z"],
["Y", "T"],
["U", "X"],
["S", "R"],
]
# 7
[docs]
class ORCF(Lattice):
r"""
Face-centred orthorhombic (ORCF, oF)
:math:`a < b < c`
Conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, b, 0)
\boldsymbol{a}_3 = (0, 0, c)
Primitive lattice:
.. math::
\boldsymbol{a}_1 = (0, b/2, c/2)
\boldsymbol{a}_2 = (a/2, 0, c/2)
\boldsymbol{a}_3 = (a/2, b/2, 0)
.. seealso::
:ref:`lattice-orcf` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional lattice.
b : float, optional
Length of the lattice vector of the conventional lattice.
c : float, optional
Length of the lattice vector of the conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_b : float
Length of the lattice vector of the conventional lattice.
conv_c : float
Length of the lattice vector of the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "oF"
def __init__(
self, a: float = None, b: float = None, c: float = None, cell=None, eps_rel=1e-5
) -> None:
if (a is None or b is None or c is None) and cell is None:
raise NotEnoughParameters("ORCF", [("a", a), ("b", b), ("c", c)])
if cell is not None:
eps = eps_rel * volume(cell) ** (1 / 3.0)
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "ORCF":
raise CellTypeMismatch(
"ORCF", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
conv_cell = [[-1.0, 1.0, 1.0], [1.0, -1.0, 1.0], [1.0, 1.0, -1.0]] @ cell
a, b, c, alpha, beta, gamma = param_from_cell(conv_cell)
if b < a - eps:
cell = [cell[1], cell[0], cell[2]]
if c < a - eps:
cell = [cell[2], cell[0], cell[1]]
elif c < b - eps:
cell = [cell[0], cell[2], cell[1]]
super().__init__(cell)
self.conv_cell = [
[-1.0, 1.0, 1.0],
[1.0, -1.0, 1.0],
[1.0, 1.0, -1.0],
] @ self.cell
a, b, c, alpha, beta, gamma = param_from_cell(self.conv_cell)
self.conv_a = a
self.conv_b = b
self.conv_c = c
else:
a, b, c = tuple(sorted([a, b, c]))
self.conv_a = a
self.conv_b = b
self.conv_c = c
super().__init__([0, b / 2, c / 2], [a / 2, 0, c / 2], [a / 2, b / 2, 0])
a, b, c, alpha, beta, gamma = param_from_cell(self.cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "ORCF":
raise CellTypeMismatch(
"ORCF", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
self.conv_cell = np.diag([self.conv_a, self.conv_b, self.conv_c])
if self.variation == "ORCF1":
eta = (
1
+ self.conv_a**2 / self.conv_b**2
+ self.conv_a**2 / self.conv_c**2
) / 4
zeta = (
1
+ self.conv_a**2 / self.conv_b**2
- self.conv_a**2 / self.conv_c**2
) / 4
self.kpoints = {
"G": np.array([0, 0, 0]),
"A": np.array([1 / 2, 1 / 2 + zeta, zeta]),
"A1": np.array([1 / 2, 1 / 2 - zeta, 1 - zeta]),
"L": np.array([1 / 2, 1 / 2, 1 / 2]),
"T": np.array([1, 1 / 2, 1 / 2]),
"X": np.array([0, eta, eta]),
"X1": np.array([1, 1 - eta, 1 - eta]),
"Y": np.array([1 / 2, 0, 1 / 2]),
"Z": np.array([1 / 2, 1 / 2, 0]),
}
self._default_path = [
["G", "Y", "T", "Z", "G", "X", "A1", "Y"],
["T", "X1"],
["X", "A", "Z"],
["L", "G"],
]
elif self.variation == "ORCF2":
eta = (
1
+ self.conv_a**2 / self.conv_b**2
- self.conv_a**2 / self.conv_c**2
) / 4
delta = (
1
+ self.conv_b**2 / self.conv_a**2
- self.conv_b**2 / self.conv_c**2
) / 4
phi = (
1
+ self.conv_c**2 / self.conv_b**2
- self.conv_c**2 / self.conv_a**2
) / 4
self.kpoints = {
"G": np.array([0, 0, 0]),
"C": np.array([1 / 2, 1 / 2 - eta, 1 - eta]),
"C1": np.array([1 / 2, 1 / 2 + eta, eta]),
"D": np.array([1 / 2 - delta, 1 / 2, 1 - delta]),
"D1": np.array([1 / 2 + delta, 1 / 2, delta]),
"L": np.array([1 / 2, 1 / 2, 1 / 2]),
"H": np.array([1 - phi, 1 / 2 - phi, 1 / 2]),
"H1": np.array([phi, 1 / 2 + phi, 1 / 2]),
"X": np.array([0, 1 / 2, 1 / 2]),
"Y": np.array([1 / 2, 0, 1 / 2]),
"Z": np.array([1 / 2, 1 / 2, 0]),
}
self._default_path = [
["G", "Y", "C", "D", "X", "G", "Z", "D1", "H", "C"],
["C1", "Z"],
["X", "H1"],
["H", "Y"],
["L", "G"],
]
elif self.variation == "ORCF3":
eta = (
1
+ self.conv_a**2 / self.conv_b**2
+ self.conv_a**2 / self.conv_c**2
) / 4
zeta = (
1
+ self.conv_a**2 / self.conv_b**2
- self.conv_a**2 / self.conv_c**2
) / 4
self.kpoints = {
"G": np.array([0, 0, 0]),
"A": np.array([1 / 2, 1 / 2 + zeta, zeta]),
"A1": np.array([1 / 2, 1 / 2 - zeta, 1 - zeta]),
"L": np.array([1 / 2, 1 / 2, 1 / 2]),
"T": np.array([1, 1 / 2, 1 / 2]),
"X": np.array([0, eta, eta]),
"Y": np.array([1 / 2, 0, 1 / 2]),
"Z": np.array([1 / 2, 1 / 2, 0]),
}
self._default_path = [
["G", "Y", "T", "Z", "G", "X", "A1", "Y"],
["X", "A", "Z"],
["L", "G"],
]
@property
def variation(self):
r"""
Three variations of the Lattice.
:math:`\text{ORCF}_1: \dfrac{1}{a^2} > \dfrac{1}{b^2} + \dfrac{1}{c^2}`,
:math:`\text{ORCF}_2: \dfrac{1}{a^2} < \dfrac{1}{b^2} + \dfrac{1}{c^2}`,
:math:`\text{ORCF}_3: \dfrac{1}{a^2} = \dfrac{1}{b^2} + \dfrac{1}{c^2}`,
Returns
-------
variation : str
Variation of the lattice. "ORCF1", "ORCF2" or "ORCF3".
"""
expresion = 1 / self.conv_a**2 - 1 / self.conv_b**2 - 1 / self.conv_c**2
if np.abs(expresion) < 10 * np.finfo(float).eps:
return "ORCF3"
elif expresion > 0:
return "ORCF1"
elif expresion < 0:
return "ORCF2"
# 8
[docs]
class ORCI(Lattice):
r"""
Body-centred orthorhombic (ORCI, oI)
:math:`a < b < c`
Conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, b, 0)
\boldsymbol{a}_3 = (0, 0, c)
Primitive lattice:
.. math::
\boldsymbol{a}_1 = (-a/2, b/2, c/2)
\boldsymbol{a}_2 = (a/2, -b/2, c/2)
\boldsymbol{a}_3 = (a/2, b/2, -c/2)
.. seealso::
:ref:`lattice-orci` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional lattice.
b : float, optional
Length of the lattice vector of the conventional lattice.
c : float, optional
Length of the lattice vector of the conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_b : float
Length of the lattice vector of the conventional lattice.
conv_c : float
Length of the lattice vector of the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "oI"
def __init__(
self, a: float = None, b: float = None, c: float = None, cell=None, eps_rel=1e-5
) -> None:
if (a is None or b is None or c is None) and cell is None:
raise NotEnoughParameters("ORCI", [("a", a), ("b", b), ("c", c)])
if cell is not None:
eps = eps_rel * volume(cell) ** (1 / 3.0)
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "ORCI":
raise CellTypeMismatch(
"ORCI", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
conv_cell = [[0, 1.0, 1.0], [1.0, 0, 1.0], [1.0, 1.0, 0]] @ cell
a, b, c, alpha, beta, gamma = param_from_cell(conv_cell)
if b < a - eps:
cell = [cell[1], cell[0], cell[2]]
if c < a - eps:
cell = [cell[2], cell[0], cell[1]]
elif c < b - eps:
cell = [cell[0], cell[2], cell[1]]
super().__init__(cell)
self.conv_cell = [
[0, 1.0, 1.0],
[1.0, 0, 1.0],
[1.0, 1.0, 0],
] @ self.cell
a, b, c, alpha, beta, gamma = param_from_cell(self.conv_cell)
self.conv_a = a
self.conv_b = b
self.conv_c = c
else:
a, b, c = tuple(sorted([a, b, c]))
self.conv_a = a
self.conv_b = b
self.conv_c = c
super().__init__(
[-a / 2, b / 2, c / 2], [a / 2, -b / 2, c / 2], [a / 2, b / 2, -c / 2]
)
a, b, c, alpha, beta, gamma = param_from_cell(self.cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "ORCI":
raise CellTypeMismatch(
"ORCI", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
self.conv_cell = np.diag([self.conv_a, self.conv_b, self.conv_c])
zeta = (1 + self.conv_a**2 / self.conv_c**2) / 4
eta = (1 + self.conv_b**2 / self.conv_c**2) / 4
delta = (self.conv_b**2 - self.conv_a**2) / (4 * self.conv_c**2)
mu = (self.conv_a**2 + self.conv_b**2) / (4 * self.conv_c**2)
self.kpoints = {
"G": np.array([0, 0, 0]),
"L": np.array([-mu, mu, 1 / 2 - delta]),
"L1": np.array([mu, -mu, 1 / 2 + delta]),
"L2": np.array([1 / 2 - delta, 1 / 2 + delta, -mu]),
"R": np.array([0, 1 / 2, 0]),
"S": np.array([1 / 2, 0, 0]),
"T": np.array([0, 0, 1 / 2]),
"W": np.array([1 / 4, 1 / 4, 1 / 4]),
"X": np.array([-zeta, zeta, zeta]),
"X1": np.array([zeta, 1 - zeta, -zeta]),
"Y": np.array([eta, -eta, eta]),
"Y1": np.array([1 - eta, eta, -eta]),
"Z": np.array([1 / 2, 1 / 2, -1 / 2]),
}
self._default_path = [
["G", "X", "L", "T", "W", "R", "X1", "Z", "G", "Y", "S", "W"],
["L1", "Y"],
["Y1", "Z"],
]
# 9
[docs]
class ORCC(Lattice):
r"""
C-centred orthorhombic (ORCC, oS)
:math:`a < b`
Conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, b, 0)
\boldsymbol{a}_3 = (0, 0, c)
Primitive lattice:
.. math::
\boldsymbol{a}_1 = (a/2, -b/2, 0)
\boldsymbol{a}_2 = (a/2, b/2, 0)
\boldsymbol{a}_3 = (0, 0, c)
.. seealso::
:ref:`lattice-orcc` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional lattice.
b : float, optional
Length of the lattice vector of the conventional lattice.
c : float, optional
Length of the lattice vector of the conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_b : float
Length of the lattice vector of the conventional lattice.
conv_c : float
Length of the lattice vector of the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "oS"
def __init__(
self, a: float = None, b: float = None, c: float = None, cell=None, eps_rel=1e-5
) -> None:
if (a is None or b is None or c is None) and cell is None:
raise NotEnoughParameters("ORCC", [("a", a), ("b", b), ("c", c)])
if cell is not None:
eps = eps_rel * volume(cell) ** (1 / 3.0)
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "ORCC":
raise CellTypeMismatch(
"ORCC", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
# a == c
if not (a < c - eps or c < a - eps):
cell = [cell[0], cell[2], cell[1]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
# b = c
elif not (b < c - eps or c < b - eps):
cell = [cell[1], cell[2], cell[0]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
conv_cell = [[1.0, 1.0, 0], [-1.0, 1.0, 0], [0, 0, 1.0]] @ cell
a, b, c, alpha, beta, gamma = param_from_cell(conv_cell)
# b < a
if b < a - eps:
cell = [cell[1], cell[0], cell[2]]
super().__init__(cell)
self.conv_cell = [[1.0, 1.0, 0], [-1.0, 1.0, 0], [0, 0, 1.0]] @ self.cell
a, b, c, alpha, beta, gamma = param_from_cell(self.conv_cell)
self.conv_a = a
self.conv_b = b
self.conv_c = c
else:
a, b = tuple(sorted([a, b]))
self.conv_a = a
self.conv_b = b
self.conv_c = c
super().__init__([a / 2, -b / 2, 0], [a / 2, b / 2, 0], [0, 0, c])
a, b, c, alpha, beta, gamma = param_from_cell(self.cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "ORCC":
raise CellTypeMismatch(
"ORCC", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
self.conv_cell = np.diag([self.conv_a, self.conv_b, self.conv_c])
zeta = (1 + self.conv_a**2 / self.conv_b**2) / 4
self.kpoints = {
"G": np.array([0, 0, 0]),
"A": np.array([zeta, zeta, 1 / 2]),
"A1": np.array([-zeta, 1 - zeta, 1 / 2]),
"R": np.array([0, 1 / 2, 1 / 2]),
"S": np.array([0, 1 / 2, 0]),
"T": np.array([-1 / 2, 1 / 2, 1 / 2]),
"X": np.array([zeta, zeta, 0]),
"X1": np.array([-zeta, 1 - zeta, 0]),
"Y": np.array([-1 / 2, 1 / 2, 0]),
"Z": np.array([0, 0, 1 / 2]),
}
self._default_path = [
["G", "X", "S", "R", "A", "Z", "G", "Y", "X1", "A1", "T", "Y"],
["Z", "T"],
]
# 10
[docs]
class HEX(Lattice):
r"""
Hexagonal (HEX, hP)
Primitive and conventional lattice:
.. math::
\boldsymbol{a}_1 = (a/2, -a\sqrt{3}, 0)
\boldsymbol{a}_2 = (a/2, a\sqrt{3}, 0)
\boldsymbol{a}_3 = (0, 0, c)
.. seealso::
:ref:`lattice-hex` for more information.
Parameters
----------
a : float, None
Length of the lattice vector of the conventional lattice.
c : float, None
Length of the lattice vector of the conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_c : float
Length of the lattice vector of the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "hP"
def __init__(
self, a: float = None, c: float = None, cell=None, eps_rel=1e-5
) -> None:
if (a is None or c is None) and cell is None:
raise NotEnoughParameters("HEX", [("a", a), ("c", c)])
if cell is not None:
eps = eps_rel * volume(cell) ** (1 / 3.0)
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "HEX":
raise CellTypeMismatch(
"HEX", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
if not (a < c - eps or c < a - eps):
cell = [cell[0], cell[2], cell[1]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
elif not (b < c - eps or c < b - eps):
cell = [cell[1], cell[2], cell[0]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
super().__init__(cell)
self.conv_a = a
self.conv_b = b
self.conv_c = c
else:
self.conv_a = a
self.conv_c = c
super().__init__(
[a / 2, -a * sqrt(3) / 2, 0], [a / 2, a * sqrt(3) / 2, 0], [0, 0, c]
)
a, b, c, alpha, beta, gamma = param_from_cell(self.cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "HEX":
raise CellTypeMismatch(
"HEX", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
self.conv_cell = self.cell
self.kpoints = {
"G": np.array([0, 0, 0]),
"A": np.array([0, 0, 1 / 2]),
"H": np.array([1 / 3, 1 / 3, 1 / 2]),
"K": np.array([1 / 3, 1 / 3, 0]),
"L": np.array([1 / 2, 0, 1 / 2]),
"M": np.array([1 / 2, 0, 0]),
}
self._default_path = [
["G", "M", "K", "G", "A", "L", "H", "A"],
["L", "M"],
["K", "H"],
]
# 11
[docs]
class RHL(Lattice):
r"""
Rhombohedral (RHL, hR)
Primitive and conventional lattice:
.. math::
\boldsymbol{a}_1 = (a\cos(\alpha / 2), -a\sin(\alpha/2), 0)
\boldsymbol{a}_2 = (a\cos(\alpha / 2), a\sin(\alpha/2), 0)
\boldsymbol{a}_3 = (\frac{\cos(\alpha)}{\cos(\alpha/2)}, 0, a\sqrt{1 - \frac{\cos^2(\alpha)}{\cos^2(\alpha/2)}})
.. seealso::
:ref:`lattice-rhl` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional lattice.
alpha : float, optional
Angle between b and c. In degrees. Corresponds to the conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_alpha : float
Angle between b and c. In degrees. Corresponds to the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "hR"
def __init__(
self, a: float = None, alpha: float = None, cell=None, eps_rel=1e-5
) -> None:
if (a is None or alpha is None) and cell is None:
raise NotEnoughParameters("RHL", [("a", a), ("alpha", alpha)])
if cell is not None:
a, b, c, alpha, beta, gamma = param_from_cell(cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "RHL":
raise CellTypeMismatch(
"RHL", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
super().__init__(cell)
self.conv_a = (a + b + c) / 3
self.conv_alpha = (alpha + beta + gamma) / 3
else:
if alpha >= 120:
raise ValueError("alpha has to be < 120 degrees.")
self.conv_a = a
self.conv_alpha = alpha
super().__init__(
[a * cos(alpha / 180 * pi / 2), -a * sin(alpha / 180 * pi / 2), 0],
[a * cos(alpha / 180 * pi / 2), a * sin(alpha / 180 * pi / 2), 0],
[
a * cos(alpha / 180 * pi) / cos(alpha / 180 * pi / 2),
0,
a
* sqrt(
1 - cos(alpha / 180 * pi) ** 2 / cos(alpha / 180 * pi / 2) ** 2
),
],
)
a, b, c, alpha, beta, gamma = param_from_cell(self.cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "RHL":
raise CellTypeMismatch(
"RHL", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
self.conv_cell = self.cell
if self.variation == "RHL1":
eta = (1 + 4 * cos(alpha * toradians)) / (2 + 4 * cos(alpha * toradians))
nu = 3 / 4 - eta / 2
self.kpoints = {
"G": np.array([0, 0, 0]),
"B": np.array([eta, 1 / 2, 1 - eta]),
"B1": np.array([1 / 2, 1 - eta, eta - 1]),
"F": np.array([1 / 2, 1 / 2, 0]),
"L": np.array([1 / 2, 0, 0]),
"L1": np.array([0, 0, -1 / 2]),
"P": np.array([eta, nu, nu]),
"P1": np.array([1 - nu, 1 - nu, 1 - eta]),
"P2": np.array([nu, nu, eta - 1]),
"Q": np.array([1 - nu, nu, 0]),
"X": np.array([nu, 0, -nu]),
"Z": np.array([1 / 2, 1 / 2, 1 / 2]),
}
self._default_path = [
["G", "L", "B1"],
["B", "Z", "G", "X"],
["Q", "F", "P1", "Z"],
["L", "P"],
]
elif self.variation == "RHL2":
eta = 1 / (2 * tan(alpha * toradians / 2) ** 2)
nu = 3 / 4 - eta / 2
self.kpoints = {
"G": np.array([0, 0, 0]),
"F": np.array([1 / 2, -1 / 2, 0]),
"L": np.array([1 / 2, 0, 0]),
"P": np.array([1 - nu, -nu, 1 - nu]),
"P1": np.array([nu, nu - 1, nu - 1]),
"Q": np.array([eta, eta, eta]),
"Q1": np.array([1 - eta, -eta, -eta]),
"Z": np.array([1 / 2, -1 / 2, 1 / 2]),
}
self._default_path = [["G", "P", "Z", "Q", "G", "F", "P1", "Q1", "L", "Z"]]
@property
def variation(self):
r"""
Two variations of the Lattice.
:math:`\text{RHL}_1 \alpha < 90^{\circ}`,
:math:`\text{RHL}_2 \alpha > 90^{\circ}`
Returns
-------
variation : str
Variation of the lattice. Either "RHL1" or "RHL2".
"""
if self.conv_alpha < 90:
return "RHL1"
elif self.conv_alpha > 90:
return "RHL2"
# 12
[docs]
class MCL(Lattice):
r"""
Monoclinic (MCL, mP)
:math:`a, b \le c`, :math:`\alpha < 90^{\circ}`, :math:`\beta = \gamma = 90^{\circ}`.
Primitive and conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, b, 0)
\boldsymbol{a}_3 = (0, c\cos(\alpha), c\sin(\alpha))
.. seealso::
:ref:`lattice-mcl` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional lattice.
b : float, optional
Length of the lattice vector of the conventional lattice.
c : float, optional
Length of the lattice vector of the conventional lattice.
alpha : float, optional
Angle between b and c. In degrees. Corresponds to the conventional lattice.
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_b : float
Length of the lattice vector of the conventional lattice.
conv_c : float
Length of the lattice vector of the conventional lattice.
conv_alpha : float
Angle between b and c. In degrees. Corresponds to the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "mP"
def __init__(
self, a: float, b: float, c: float, alpha: float, cell=None, eps_rel=1e-5
) -> None:
if (a is None or alpha is None) and cell is None:
raise NotEnoughParameters(
"MCL", [("a", a), ("b", b), ("c", c), ("alpha", alpha)]
)
if cell is not None:
a, b, c, alpha, beta, gamma = param_from_cell(cell)
eps = eps_rel * volume(cell) ** (1 / 3.0)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "MCL":
raise CellTypeMismatch(
"MCL", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
eps = eps_rel * volume(cell) ** (1 / 3.0)
# beta != 90
if cos(beta * toradians) + eps < 0 or 0 < cos(beta * toradians) - eps:
cell = [cell[1], cell[2], cell[0]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
# gamma != 90
elif cos(gamma * toradians) + eps < 0 or 0 < cos(gamma * toradians) - eps:
cell = [cell[2], cell[0], cell[1]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
# alpha > 90
if cos(alpha * toradians) < -eps:
cell = [cell[0], cell[2], -cell[1]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
# b > c
if c < b - eps:
cell = [-cell[0], cell[2], cell[1]]
a, b, c, alpha, beta, gamma = param_from_cell(cell)
super().__init__(cell)
self.conv_a = a
self.conv_b = b
self.conv_c = c
self.conv_alpha = alpha
else:
eps = eps_rel * volume(a, b, c, alpha, 90, 90) ** (1 / 3.0)
b, c = tuple(sorted([b, c]))
if cos(alpha * toradians) < -eps:
alpha = alpha - 90
self.conv_a = a
self.conv_b = b
self.conv_c = c
self.conv_alpha = alpha
super().__init__(
[a, 0, 0],
[0, b, 0],
[0, c * cos(alpha * toradians), c * sin(alpha * toradians)],
)
a, b, c, alpha, beta, gamma = param_from_cell(self.cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "MCL":
raise CellTypeMismatch(
"MCL", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
self.conv_cell = self.cell
eta = (1 - b * cos(alpha * toradians) / c) / (2 * sin(alpha * toradians) ** 2)
nu = 1 / 2 - eta * c * cos(alpha * toradians) / b
self.kpoints = {
"G": np.array([0, 0, 0]),
"A": np.array([1 / 2, 1 / 2, 0]),
"C": np.array([0, 1 / 2, 1 / 2]),
"D": np.array([1 / 2, 0, 1 / 2]),
"D1": np.array([1 / 2, 0, -1 / 2]),
"E": np.array([1 / 2, 1 / 2, 1 / 2]),
"H": np.array([0, eta, 1 - nu]),
"H1": np.array([0, 1 - eta, nu]),
"H2": np.array([0, eta, -nu]),
"M": np.array([1 / 2, eta, 1 - nu]),
"M1": np.array([1 / 2, 1 - eta, nu]),
"M2": np.array([1 / 2, eta, -nu]),
"X": np.array([0, 1 / 2, 0]),
"Y": np.array([0, 0, 1 / 2]),
"Y1": np.array([0, 0, -1 / 2]),
"Z": np.array([1 / 2, 0, 0]),
}
self._default_path = [
["G", "Y", "H", "C", "E", "M1", "A", "X", "H1"],
["M", "D", "Z"],
["Y", "D"],
]
# 13
[docs]
class MCLC(Lattice):
r"""
C-centred monoclinic (MCLC, mS)
:math:`a, b \le c`, :math:`\alpha < 90^{\circ}`, :math:`\beta = \gamma = 90^{\circ}`.
Conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (0, b, 0)
\boldsymbol{a}_3 = (0, c\cos(\alpha), c\sin(\alpha))
Primitive lattice:
.. math::
\boldsymbol{a}_1 = (a/2, b/2, 0)
\boldsymbol{a}_2 = (-a/2, b/2, 0)
\boldsymbol{a}_3 = (0, c\cos(\alpha), c\sin(\alpha))
.. seealso::
:ref:`lattice-mclc` for more information.
Parameters
----------
a : float, optional
Length of the lattice vector of the conventional lattice.
b : float, optional
Length of the lattice vector of the conventional lattice.
c : float, optional
Length of the lattice vector of the conventional lattice.
alpha : float, optional
Angle between b and c. In degrees. Corresponds to the conventional lattice
cell : (3,3) |array_like|_, optional
Primitive cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
eps_rel : float, default 1e-5
Relative epsilon as defined in [1]_.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_b : float
Length of the lattice vector of the conventional lattice.
conv_c : float
Length of the lattice vector of the conventional lattice.
conv_alpha : float
Angle between b and c. In degrees. Corresponds to the conventional lattice
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
References
----------
.. [1] Grosse-Kunstleve, R.W., Sauter, N.K. and Adams, P.D., 2004.
Numerically stable algorithms for the computation of reduced unit cells.
Acta Crystallographica Section A: Foundations of Crystallography,
60(1), pp.1-6.
"""
_pearson_symbol = "mS"
def __init__(
self, a: float, b: float, c: float, alpha: float, cell=None, eps_rel=1e-5
) -> None:
if (a is None or alpha is None) and cell is None:
raise NotEnoughParameters(
"MCLC", [("a", a), ("b", b), ("c", c), ("alpha", alpha)]
)
if cell is not None:
a, b, c, alpha, beta, gamma = param_from_cell(cell)
eps = eps_rel * volume(cell) ** (1 / 3.0)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "MCLC":
raise CellTypeMismatch(
"MCLC", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
eps = eps_rel * volume(cell) ** (1 / 3.0)
# a == c
if not (a < c - eps or c < a - eps):
cell = [cell[2], cell[1], cell[1]]
# b == c
elif not (b < c - eps or c < b - eps):
cell = [cell[1], cell[2], cell[0]]
conv_cell = [[1.0, -1.0, 0.0], [1.0, 1.0, 0.0], [0.0, 0.0, 1.0]] @ cell
a, b, c, alpha, beta, gamma = param_from_cell(conv_cell)
# alpha > 90
if cos(alpha * toradians) < -eps:
cell = [cell[0], cell[2], -cell[1]]
conv_cell = [[1.0, -1.0, 0.0], [1.0, 1.0, 0.0], [0.0, 0.0, 1.0]] @ cell
a, b, c, alpha, beta, gamma = param_from_cell(conv_cell)
# b > c
if c < b - eps:
cell = [-cell[0], cell[2], cell[1]]
conv_cell = [[1.0, -1.0, 0.0], [1.0, 1.0, 0.0], [0.0, 0.0, 1.0]] @ cell
a, b, c, alpha, beta, gamma = param_from_cell(conv_cell)
super().__init__(cell)
self.conv_cell = [
[1.0, -1.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
] @ self.cell
self.conv_a = a
self.conv_b = b
self.conv_c = c
self.conv_alpha = alpha
else:
eps = eps_rel * volume(a, b, c, alpha, 90, 90) ** (1 / 3.0)
b, c = tuple(sorted([b, c]))
if cos(alpha * toradians) < -eps:
alpha = alpha - 90
self.conv_a = a
self.conv_b = b
self.conv_c = c
self.conv_alpha = alpha
super().__init__(
[a / 2, b / 2, 0],
[-a / 2, b / 2, 0],
[
0,
c * cos(alpha * toradians),
c * sin(alpha * toradians),
],
)
self.conv_cell = np.array(
[
[self.conv_a, 0, 0],
[0, self.conv_b, 0],
[
0,
self.conv_c * cos(alpha * toradians),
self.conv_c * sin(alpha * toradians),
],
]
)
a, b, c, alpha, beta, gamma = param_from_cell(self.cell)
lattice_type = lepage(a, b, c, alpha, beta, gamma, eps_rel=eps_rel)
if lattice_type != "MCLC":
raise CellTypeMismatch(
"MCLC", eps_rel, a, b, c, alpha, beta, gamma, lattice_type
)
# Parameters
if self.variation in ["MCLC1", "MCLC2"]:
zeta = (
2 - self.conv_b * cos(self.conv_alpha * toradians) / self.conv_c
) / (4 * sin(self.conv_alpha * toradians) ** 2)
eta = (
1 / 2
+ 2
* zeta
* self.conv_c
* cos(self.conv_alpha * toradians)
/ self.conv_b
)
psi = 3 / 4 - self.conv_a**2 / (
4 * self.conv_b**2 * sin(self.conv_alpha * toradians) ** 2
)
phi = (
psi + (3 / 4 - psi) * self.conv_b * cos(self.conv_alpha * toradians) / c
)
elif self.variation in ["MCLC3", "MCLC4"]:
mu = (1 + self.conv_b**2 / self.conv_a**2) / 4
delta = (
self.conv_b
* c
* cos(self.conv_alpha * toradians)
/ (2 * self.conv_a**2)
)
zeta = (
mu
- 1 / 4
+ (1 - self.conv_b * cos(self.conv_alpha * toradians) / self.conv_c)
/ (4 * sin(self.conv_alpha * toradians) ** 2)
)
eta = (
1 / 2
+ 2
* zeta
* self.conv_c
* cos(self.conv_alpha * toradians)
/ self.conv_b
)
phi = 1 + zeta - 2 * mu
psi = eta - 2 * delta
elif self.variation == "MCLC5":
zeta = (
self.conv_b**2 / self.conv_a**2
+ (1 - self.conv_b * cos(self.conv_alpha * toradians) / self.conv_c)
/ sin(self.conv_alpha * toradians) ** 2
) / 4
eta = (
1 / 2
+ 2
* zeta
* self.conv_c
* cos(self.conv_alpha * toradians)
/ self.conv_b
)
mu = (
eta / 2
+ self.conv_b**2 / (4 * self.conv_a**2)
- self.conv_b
* self.conv_c
* cos(self.conv_alpha * toradians)
/ (2 * self.conv_a**2)
)
nu = 2 * mu - zeta
rho = 1 - zeta * self.conv_a**2 / self.conv_b**2
omega = (
(
4 * nu
- 1
- self.conv_b**2
* sin(self.conv_alpha * toradians) ** 2
/ self.conv_a**2
)
* self.conv_c
/ (2 * self.conv_b * cos(self.conv_alpha * toradians))
)
delta = (
zeta * self.conv_c * cos(self.conv_alpha * toradians) / self.conv_b
+ omega / 2
- 1 / 4
)
# Path
if self.variation == "MCLC1":
self._default_path = [
["G", "Y", "F", "L", "I"],
["I1", "Z", "F1"],
["Y", "X1"],
["X", "G", "N"],
["M", "G"],
]
self.kpoints = {
"G": np.array([0, 0, 0]),
"N": np.array([1 / 2, 0, 0]),
"N1": np.array([0, -1 / 2, 0]),
"F": np.array([1 - zeta, 1 - zeta, 1 - eta]),
"F1": np.array([zeta, zeta, eta]),
"F2": np.array([-zeta, -zeta, 1 - eta]),
"I": np.array([phi, 1 - phi, 1 / 2]),
"I1": np.array([1 - phi, phi - 1, 1 / 2]),
"L": np.array([1 / 2, 1 / 2, 1 / 2]),
"M": np.array([1 / 2, 0, 1 / 2]),
"X": np.array([1 - psi, psi - 1, 0]),
"X1": np.array([psi, 1 - psi, 0]),
"X2": np.array([psi - 1, -psi, 0]),
"Y": np.array([1 / 2, 1 / 2, 0]),
"Y1": np.array([-1 / 2, -1 / 2, 0]),
"Z": np.array([0, 0, 1 / 2]),
}
elif self.variation == "MCLC2":
self._default_path = [
["G", "Y", "F", "L", "I"],
["I1", "Z", "F1"],
["N", "G", "M"],
]
self.kpoints = {
"G": np.array([0, 0, 0]),
"N": np.array([1 / 2, 0, 0]),
"N1": np.array([0, -1 / 2, 0]),
"F": np.array([1 - zeta, 1 - zeta, 1 - eta]),
"F1": np.array([zeta, zeta, eta]),
"F2": np.array([-zeta, -zeta, 1 - eta]),
"F3": np.array([1 - zeta, -zeta, 1 - eta]),
"I": np.array([phi, 1 - phi, 1 / 2]),
"I1": np.array([1 - phi, phi - 1, 1 / 2]),
"L": np.array([1 / 2, 1 / 2, 1 / 2]),
"M": np.array([1 / 2, 0, 1 / 2]),
"X": np.array([1 - psi, psi - 1, 0]),
"Y": np.array([1 / 2, 1 / 2, 0]),
"Y1": np.array([-1 / 2, -1 / 2, 0]),
"Z": np.array([0, 0, 1 / 2]),
}
elif self.variation == "MCLC3":
self.kpoints = {
"G": np.array([0, 0, 0]),
"F": np.array([1 - phi, 1 - phi, 1 - psi]),
"F1": np.array([phi, phi - 1, psi]),
"F2": np.array([1 - phi, -phi, 1 - psi]),
"H": np.array([zeta, zeta, eta]),
"H1": np.array([1 - zeta, -zeta, 1 - eta]),
"H2": np.array([-zeta, -zeta, 1 - eta]),
"I": np.array([1 / 2, -1 / 2, 1 / 2]),
"M": np.array([1 / 2, 0, 1 / 2]),
"N": np.array([1 / 2, 0, 0]),
"N1": np.array([0, -1 / 2, 0]),
"X": np.array([1 / 2, -1 / 2, 0]),
"Y": np.array([mu, mu, delta]),
"Y1": np.array([1 - mu, -mu, -delta]),
"Y2": np.array([-mu, -mu, -delta]),
"Y3": np.array([mu, mu - 1, delta]),
"Z": np.array([0, 0, 1 / 2]),
}
self._default_path = [
["G", "Y", "F", "H", "Z", "I", "F1"],
["H1", "Y1", "X", "G", "N"],
["M", "G"],
]
elif self.variation == "MCLC4":
self.kpoints = {
"G": np.array([0, 0, 0]),
"F": np.array([1 - phi, 1 - phi, 1 - psi]),
"H": np.array([zeta, zeta, eta]),
"H1": np.array([1 - zeta, -zeta, 1 - eta]),
"H2": np.array([-zeta, -zeta, 1 - eta]),
"I": np.array([1 / 2, -1 / 2, 1 / 2]),
"M": np.array([1 / 2, 0, 1 / 2]),
"N": np.array([1 / 2, 0, 0]),
"N1": np.array([0, -1 / 2, 0]),
"X": np.array([1 / 2, -1 / 2, 0]),
"Y": np.array([mu, mu, delta]),
"Y1": np.array([1 - mu, -mu, -delta]),
"Y2": np.array([-mu, -mu, -delta]),
"Y3": np.array([mu, mu - 1, delta]),
"Z": np.array([0, 0, 1 / 2]),
}
self._default_path = [
["G", "Y", "F", "H", "Z", "I"],
["H1", "Y1", "X", "G", "N"],
["M", "G"],
]
elif self.variation == "MCLC5":
self._default_path = [
["G", "Y", "F", "L", "I"],
["I1", "Z", "H", "F1"],
["H1", "Y1", "X", "G", "N"],
["M", "G"],
]
self.kpoints = {
"G": np.array([0, 0, 0]),
"F": np.array([nu, nu, omega]),
"F1": np.array([1 - nu, 1 - nu, 1 - omega]),
"F2": np.array([nu, nu - 1, omega]),
"H": np.array([zeta, zeta, eta]),
"H1": np.array([1 - zeta, -zeta, 1 - eta]),
"H2": np.array([-zeta, -zeta, 1 - eta]),
"I": np.array([rho, 1 - rho, 1 / 2]),
"I1": np.array([1 - rho, rho - 1, 1 / 2]),
"L": np.array([1 / 2, 1 / 2, 1 / 2]),
"M": np.array([1 / 2, 0, 1 / 2]),
"N": np.array([1 / 2, 0, 0]),
"N1": np.array([0, -1 / 2, 0]),
"X": np.array([1 / 2, -1 / 2, 0]),
"Y": np.array([mu, mu, delta]),
"Y1": np.array([1 - mu, -mu, -delta]),
"Y2": np.array([-mu, -mu, -delta]),
"Y3": np.array([mu, mu - 1, delta]),
"Z": np.array([0, 0, 1 / 2]),
}
@property
def variation(self):
r"""
Five variation of the Lattice.
:math:`\text{MCLC}_1: k_{\gamma} > 90^{\circ}`,
:math:`\text{MCLC}_2: k_{\gamma} = 90^{\circ}`,
:math:`\text{MCLC}_3: k_{\gamma} < 90^{\circ}, \dfrac{b\cos(\alpha)}{c} + \dfrac{b^2\sin(\alpha)^2}{a^2} < 1`
:math:`\text{MCLC}_4: k_{\gamma} < 90^{\circ}, \dfrac{b\cos(\alpha)}{c} + \dfrac{b^2\sin(\alpha)^2}{a^2} = 1`
:math:`\text{MCLC}_5: k_{\gamma} < 90^{\circ}, \dfrac{b\cos(\alpha)}{c} + \dfrac{b^2\sin(\alpha)^2}{a^2} > 1`
Returns
-------
variation : str
Variation of the lattice.
Either "MCLC1", "MCLC2", "MCLC3", "MCLC4" or "MCLC5".
"""
if np.abs(self.k_gamma - 90) <= 10 * np.finfo(float).eps:
return "MCLC2"
elif self.k_gamma > 90:
return "MCLC1"
elif self.k_gamma < 90:
expression = (
self.conv_b * cos(self.conv_alpha * toradians) / self.conv_c
+ self.conv_b**2
* sin(self.conv_alpha * toradians) ** 2
/ self.conv_a**2
)
if np.abs(expression - 1) <= 10 * np.finfo(float).eps:
return "MCLC4"
elif expression < 1:
return "MCLC3"
elif expression > 1:
return "MCLC5"
# 14
[docs]
class TRI(Lattice):
r"""
Triclinic (TRI, aP)
Primitive and conventional lattice:
.. math::
\boldsymbol{a}_1 = (a, 0, 0)
\boldsymbol{a}_2 = (b\cos(\gamma), b\sin(\gamma), 0)
\boldsymbol{a}_3 = (c\cos(\beta), \frac{c(\cos(\alpha) - \cos(\beta)\cos(\gamma))}{\sin{\gamma}}, \frac{c}{\sin(\gamma)}\sqrt{\sin^2(\gamma) - \cos^2(\alpha) - \cos^2(\beta) + 2\cos(\alpha)\cos(\beta)\cos(\gamma)})
Variations of the trigonal lattice are defined through the angles of the reciprocal cell,
therefore it is possible to define trigonal Bravais lattice with reciprocal cell parameters
(argument ``reciprocal``).
.. seealso::
:ref:`lattice-tri` for more information.
Parameters
----------
a : float
Length of the lattice vector of the conventional lattice.
b : float
Length of the lattice vector of the conventional lattice.
c : float
Length of the lattice vector of the conventional lattice.
alpha : float
Angle between b and c. In degrees. Corresponds to the conventional lattice.
beta : float
Angle between a and c. In degrees. Corresponds to the conventional lattice.
gamma : float
Angle between a and b. In degrees. Corresponds to the conventional lattice.
reciprocal : bool, default False
Whether to interpret ``a``, ``b``, ``c``, ``alpha``, ``beta``, ``gamma``
as reciprocal lattice parameters.
Attributes
----------
conv_a : float
Length of the lattice vector of the conventional lattice.
conv_b : float
Length of the lattice vector of the conventional lattice.
conv_c : float
Length of the lattice vector of the conventional lattice.
conv_alpha : float
Angle between b and c. In degrees. Corresponds to the conventional lattice.
beta : float
Angle between a and c. In degrees. Corresponds to the conventional lattice.
conv_gamma : float
Angle between a and b. In degrees. Corresponds to the conventional lattice.
conv_cell : (3,3) :numpy:`ndarray`
Conventional unit cell.
.. code-block:: python
conv_cell = [[a_x, a_y, a_z],
[b_x, b_y, b_z],
[c_x, c_y, c_z]]
"""
_pearson_symbol = "aP"
def __init__(
self,
a: float,
b: float,
c: float,
alpha: float,
beta: float,
gamma: float,
reciprocal=False,
) -> None:
if not reciprocal:
tmp = sorted([(a, alpha), (b, beta), (c, gamma)], key=lambda x: x[0])
a = tmp[0][0]
alpha = tmp[0][1]
b = tmp[1][0]
beta = tmp[1][1]
c = tmp[2][0]
gamma = tmp[2][1]
super().__init__(a, b, c, alpha, beta, gamma)
if reciprocal:
self.cell = self.reciprocal_cell
self.conv_a = a
self.conv_b = b
self.conv_c = c
self.conv_alpha = alpha
self.conv_beta = beta
self.conv_gamma = gamma
if (
self.k_alpha < self.k_gamma < self.k_beta
or self.k_beta < self.k_gamma < self.k_alpha
):
raise RuntimeError("k_gamma is not minimal nor maximal.")
self.conv_cell = self.cell
if self.variation in ["TRI1a", "TRI2a"]:
self.kpoints = {
"G": np.array([0, 0, 0]),
"L": np.array([1 / 2, 1 / 2, 0]),
"M": np.array([0, 1 / 2, 1 / 2]),
"N": np.array([1 / 2, 0, 1 / 2]),
"R": np.array([1 / 2, 1 / 2, 1 / 2]),
"X": np.array([1 / 2, 0, 0]),
"Y": np.array([0, 1 / 2, 0]),
"Z": np.array([0, 0, 1 / 2]),
}
self._default_path = [
["X", "G", "Y"],
["L", "G", "Z"],
["N", "G", "M"],
["R", "G"],
]
elif self.variation in ["TRI1b", "TRI2b"]:
self.kpoints = {
"G": np.array([0, 0, 0]),
"L": np.array([1 / 2, -1 / 2, 0]),
"M": np.array([0, 0, 1 / 2]),
"N": np.array([-1 / 2, -1 / 2, 1 / 2]),
"R": np.array([0, -1 / 2, 1 / 2]),
"X": np.array([0, -1 / 2, 0]),
"Y": np.array([1 / 2, 0, 0]),
"Z": np.array([-1 / 2, 0, 1 / 2]),
}
self._default_path = [
["X", "G", "Y"],
["L", "G", "Z"],
["N", "G", "M"],
["R", "G"],
]
@property
def variation(self):
r"""
Four variations of the Lattice.
:math:`\text{TRI}_{1a} k_{\alpha} > 90^{\circ}, k_{\beta} > 90^{\circ}, k_{\gamma} > 90^{\circ}, k_{\gamma} = \min(k_{\alpha}, k_{\beta}, k_{\gamma})`
:math:`\text{TRI}_{1b} k_{\alpha} < 90^{\circ}, k_{\beta} < 90^{\circ}, k_{\gamma} < 90^{\circ}, k_{\gamma} = \max(k_{\alpha}, k_{\beta}, k_{\gamma})`
:math:`\text{TRI}_{2a} k_{\alpha} > 90^{\circ}, k_{\beta} > 90^{\circ}, k_{\gamma} = 90^{\circ}`
:math:`\text{TRI}_{2b} k_{\alpha} < 90^{\circ}, k_{\beta} < 90^{\circ}, k_{\gamma} = 90^{\circ}`
Returns
-------
variation : str
Variation of the lattice.
Either "TRI1a", "TRI1b", "TRI2a" or "TRI2b".
"""
if self.k_gamma == 90:
if self.k_alpha > 90 and self.k_beta > 90:
return "TRI2a"
elif self.k_alpha < 90 and self.k_beta < 90:
return "TRI2b"
elif (min(self.k_gamma, self.k_beta, self.k_alpha)) > 90:
return "TRI1a"
elif (max(self.k_gamma, self.k_beta, self.k_alpha)) < 90:
return "TRI1b"
else:
return "TRI"
[docs]
def bravais_lattice_from_param(a, b, c, alpha, beta, gamma) -> Lattice:
r"""
Create Bravais lattice from lattice parameters.
Orientation is default as described in [1]_.
Parameters
----------
a : float, default 1
Length of the :math:`a_1` vector.
b : float, default 1
Length of the :math:`a_2` vector.
c : float, default 1
Length of the :math:`a_3` vector.
alpha : float, default 90
Angle between vectors :math:`a_2` and :math:`a_3`. In degrees.
beta : float, default 90
Angle between vectors :math:`a_1` and :math:`a_3`. In degrees.
gamma : float, default 90
Angle between vectors :math:`a_1` and :math:`a_2`. In degrees.
lattice_type : str
Lattice type.
Returns
-------
bravais_lattice : Lattice
Bravais lattice.
References
----------
.. [1] Setyawan, W. and Curtarolo, S., 2010.
High-throughput electronic band structure calculations: Challenges and tools.
Computational materials science, 49(2), pp.299-312.
"""
lattice_type = lepage(a, b, c, alpha, beta, gamma)
if lattice_type == "CUB":
a = (a + b + c) / 3
return CUB(a)
if lattice_type == "FCC":
a = (a + b + c) / 3
return FCC(a)
if lattice_type == "BCC":
a = (a + b + c) / 3
return BCC(a)
if lattice_type == "TET":
if a == b:
return TET((a + b) / 2, c)
elif a == c:
return TET((a + c) / 2, b)
elif b == c:
return TET((b + c) / 2, a)
if lattice_type == "BCT":
if a == b:
return BCT((a + b) / 2, c)
elif a == c:
return BCT((a + c) / 2, b)
elif b == c:
return BCT((b + c) / 2, a)
if lattice_type == "ORC":
return ORC(a, b, c)
if lattice_type == "ORCF":
return ORCF(a, b, c)
if lattice_type == "ORCC":
return ORCC(a, b, c)
if lattice_type == "ORCI":
return ORCI(a, b, c)
if lattice_type == "HEX":
if abs(a - b) < abs(a - c) and abs(a - b) < abs(b - c):
return HEX((a + b) / 2, c)
elif abs(a - c) < abs(a - b) and abs(a - c) < abs(b - c):
return HEX((a + c) / 2, b)
elif abs(b - c) < abs(a - c) and abs(b - c) < abs(a - b):
return HEX((b + c) / 2, a)
if lattice_type == "RHL":
return RHL((a + b + c) / 3, (alpha + beta + gamma) / 2)
if lattice_type == "MCL":
alpha, beta, gamma = [alpha, beta, gamma].sort()
return MCL(a, b, c, alpha)
if lattice_type == "MCLC":
alpha, beta, gamma = [alpha, beta, gamma].sort()
return MCLC(a, b, c, alpha)
return TRI(a, b, c, alpha, beta, gamma)
[docs]
def bravais_lattice_from_cell(cell) -> Lattice:
r"""
Create Bravais lattice from cell matrix.
Orientation of the cell is respected, however the lattice vectors are renamed
with respect to [1]_.
Parameters
----------
cell : (3,3) |array_like|_
Cell matrix, rows are interpreted as vectors.
.. code-block:: python
cell = [[a1_x, a1_y, a1_z],
[a2_x, a2_y, a2_z],
[a3_x, a3_y, a3_z]]
lattice_type : str
Lattice type.
Returns
-------
bravais_lattice : Lattice
Bravais lattice.
References
----------
.. [1] Setyawan, W. and Curtarolo, S., 2010.
High-throughput electronic band structure calculations: Challenges and tools.
Computational materials science, 49(2), pp.299-312.
"""
lattice_type = lepage(*param_from_cell(cell))
if lattice_type == "CUB":
return CUB(cell=cell)
if lattice_type == "FCC":
return FCC(cell=cell)
if lattice_type == "BCC":
return BCC(cell=cell)
if lattice_type == "TET":
return TET(cell=cell)
if lattice_type == "BCT":
return BCT(cell=cell)
if lattice_type == "ORC":
return ORC(cell=cell)
if lattice_type == "ORCF":
return ORCF(cell=cell)
if lattice_type == "ORCC":
return ORCC(cell=cell)
if lattice_type == "ORCI":
return ORCI(cell=cell)
if lattice_type == "HEX":
return HEX(cell=cell)
if lattice_type == "RHL":
return RHL(cell=cell)
if lattice_type == "MCL":
return MCL(cell=cell)
if lattice_type == "MCLC":
return MCLC(cell=cell)
return TRI(cell=cell)
return Lattice(cell)
[docs]
def lattice_example(
lattice=None,
):
r"""
Return an example of the lattice.
Parameters
----------
lattice : str, optional
Name of the lattice to be returned.
For available names see documentation of each Bravais lattice class.
Lowercased before usage.
Returns
-------
lattice : Lattice or list
Child of the :py:class:`.Lattice` class is returned.
If no math found a list with available examples is returned.
"""
all_examples = [
"CUB",
"FCC",
"BCC",
"TET",
"BCT1",
"BCT2",
"ORC",
"ORCF1",
"ORCF2",
"ORCF3",
"ORCI",
"ORCC",
"HEX",
"RHL1",
"RHL2",
"MCL",
"MCLC1",
"MCLC2",
"MCLC3",
"MCLC4",
"MCLC5",
"TRI1a",
"TRI2a",
"TRI1b",
"TRI2b",
]
if not isinstance(lattice, str):
return all_examples
lattice = lattice.lower()
if lattice == "cub":
return CUB(pi)
elif lattice == "fcc":
return FCC(pi)
elif lattice == "bcc":
return BCC(pi)
elif lattice == "tet":
return TET(pi, 1.5 * pi)
elif lattice in ["bct1", "bct"]:
return BCT(1.5 * pi, pi)
elif lattice == "bct2":
return BCT(pi, 1.5 * pi)
elif lattice == "orc":
return ORC(pi, 1.5 * pi, 2 * pi)
elif lattice in ["orcf1", "orcf"]:
return ORCF(0.7 * pi, 5 / 4 * pi, 5 / 3 * pi)
elif lattice == "orcf2":
return ORCF(1.2 * pi, 5 / 4 * pi, 5 / 3 * pi)
elif lattice == "orcf3":
return ORCF(pi, 5 / 4 * pi, 5 / 3 * pi)
elif lattice == "orci":
return ORCI(pi, 1.3 * pi, 1.7 * pi)
elif lattice == "orcc":
return ORCC(pi, 1.3 * pi, 1.7 * pi)
elif lattice == "hex":
return HEX(pi, 2 * pi)
elif lattice in ["rhl1", "rhl"]:
# If alpha = 60 it is effectively FCC!
return RHL(pi, 70)
elif lattice == "rhl2":
return RHL(pi, 110)
elif lattice == "mcl":
return MCL(pi, 1.3 * pi, 1.6 * pi, alpha=75)
elif lattice in ["mclc1", "mclc"]:
return MCLC(pi, 1.4 * pi, 1.7 * pi, 80)
elif lattice == "mclc2":
return MCLC(1.4 * pi * sin(75 * toradians), 1.4 * pi, 1.7 * pi, 75)
elif lattice == "mclc3":
b = pi
x = 1.1
alpha = 78
ralpha = alpha * toradians
c = b * (x**2) / (x**2 - 1) * cos(ralpha) * 1.8
a = x * b * sin(ralpha)
return MCLC(a, b, c, alpha)
elif lattice == "mclc4":
b = pi
x = 1.2
alpha = 65
ralpha = alpha * toradians
c = b * (x**2) / (x**2 - 1) * cos(ralpha)
a = x * b * sin(ralpha)
return MCLC(a, b, c, alpha)
elif lattice == "mclc5":
b = pi
x = 1.4
alpha = 53
ralpha = alpha * toradians
c = b * (x**2) / (x**2 - 1) * cos(ralpha) * 0.9
a = x * b * sin(ralpha)
return MCLC(a, b, c, alpha)
elif lattice in ["tri1a", "tri1", "tri", "tria"]:
return TRI(1, 1.5, 2, 120, 110, 100, reciprocal=True)
elif lattice in ["tri2a", "tri2"]:
return TRI(1, 1.5, 2, 120, 110, 90, reciprocal=True)
elif lattice in ["tri1b", "trib"]:
return TRI(1, 1.5, 2, 60, 70, 80, reciprocal=True)
elif lattice == "tri2b":
return TRI(1, 1.5, 2, 60, 70, 90, reciprocal=True)
else:
return all_examples
if __name__ == "__main__":
for e in lattice_example()[-4:]:
print(e)
l = lattice_example(e)
l.prepare_figure()
l.plot("brillouin_kpath", label=l.variation)
l.legend()
l.show()