# Source code for seekpath.hpkot.tools

"""Various utilities."""
import numpy
import numpy.linalg
from math import sqrt

[docs]def eval_expr_simple(expr, kparam):  # pylint=disable: too-many-return-statements
"""
To evaluate expressions tha only require kparams and not a, b, c, ...
"""
if expr == "0":
return 0.0
if expr == "1/2":
return 1.0 / 2.0
if expr == "1":
return 1.0
if expr == "-1/2":
return -1.0 / 2.0
if expr == "1/4":
return 1.0 / 4.0
if expr == "3/8":
return 3.0 / 8.0
if expr == "3/4":
return 3.0 / 4.0
if expr == "5/8":
return 5.0 / 8.0
if expr == "1/3":
return 1.0 / 3.0
try:
return kparam[expr]
except KeyError as exc:
raise ValueError(
"Asking for evaluation of symbol '{}' in "
"eval_expr_simple but this has not been defined or not "
"yet computed".format(str(exc))
)

[docs]def extend_kparam(kparam):
"""
Extend the list of kparam with also expressions like :math:1-x, ...

:param kparam: a dictionary where the key is the expression as a string and
the value is the numerical value
:return: a similar dictionary, extended with simple expressions
"""
kparam_extended = {}
for key, val in kparam.items():
kparam_extended[key] = val
kparam_extended["-{}".format(key)] = -val
kparam_extended["1-{}".format(key)] = 1.0 - val
kparam_extended["-1+{}".format(key)] = -1.0 + val
kparam_extended["1/2-{}".format(key)] = 1.0 / 2.0 - val
kparam_extended["1/2+{}".format(key)] = 1.0 / 2.0 + val

return kparam_extended

[docs]def eval_expr(  # pylint: disable=too-many-return-statements,unused-argument
expr, a, b, c, cosalpha, cosbeta, cosgamma, kparam
):
r"""
Given a string expression as a function of the parameters a, b, c (lengths of the
cell lattice vectors) and cosalpha, cosbeta, cosgamma (the cosines of the three
angles between lattice vectors) returns the numerical value of the expression.

:param a: length of the first lattice vector
:param b: length of the second lattice vector
:param c: length of the third lattice vector
:param cosalpha: cosine of the :math:\alpha angle (between lattice vectors 2 and 3)
:param cosbeta: cosine of the :math:\beta angle (between lattice vectors 1 and 3)
:param cosgamma: cosine of the :math:\gamma angle (between lattice vectors 1 and 2)
:param kparam: a dictionary that associates the value to expressions as a function
of the a, b, c, cosalpha, cosbeta, cosgamma parameters

:return: the value of the expression for the given values of the cell parameters

.. note::  To evaluate expressions, I hardcode a table of existing expressions in the
DB rather than parsing the string (to avoid additional dependencies and
avoid the use of eval).
"""
from math import sqrt

# sinalpha = sqrt(1.0 - cosalpha ** 2)
sinbeta = sqrt(1.0 - cosbeta ** 2)
# singamma = sqrt(1.0 - cosgamma ** 2)

try:
if expr == "(a*a/b/b+(1+a/c*cosbeta)/sinbeta/sinbeta)/4":
return (a * a / b / b + (1.0 + a / c * cosbeta) / sinbeta / sinbeta) / 4.0
if expr == "1-Z*b*b/a/a":
Z = kparam["Z"]
return 1.0 - Z * b * b / a / a
if expr == "1/2-2*Z*c*cosbeta/a":
Z = kparam["Z"]
return 1.0 / 2.0 - 2.0 * Z * c * cosbeta / a
if expr == "E/2+a*a/4/b/b+a*c*cosbeta/2/b/b":
E = kparam["E"]
return E / 2.0 + a * a / 4.0 / b / b + a * c * cosbeta / 2.0 / b / b
if expr == "2*F-Z":
F = kparam["F"]
Z = kparam["Z"]
return 2.0 * F - Z
if expr == "c/2/a/cosbeta*(1-4*U+a*a*sinbeta*sinbeta/b/b)":
U = kparam["U"]
return (
c
/ 2.0
/ a
/ cosbeta
* (1.0 - 4.0 * U + a * a * sinbeta * sinbeta / b / b)
)
if expr == "-1/4+W/2-Z*c*cosbeta/a":
W = kparam["W"]
Z = kparam["Z"]
return -1.0 / 4.0 + W / 2.0 - Z * c * cosbeta / a
if expr == "(2+a/c*cosbeta)/4/sinbeta/sinbeta":
return (2.0 + a / c * cosbeta) / 4.0 / sinbeta / sinbeta
if expr == "3/4-b*b/4/a/a/sinbeta/sinbeta":
return 3.0 / 4.0 - b * b / 4.0 / a / a / sinbeta / sinbeta
if expr == "S-(3/4-S)*a*cosbeta/c":
S = kparam["S"]
return S - (3.0 / 4.0 - S) * a * cosbeta / c
if expr == "(1+a*a/b/b)/4":
return (1.0 + a * a / b / b) / 4.0
if expr == "-a*c*cosbeta/2/b/b":
return -a * c * cosbeta / 2.0 / b / b
if expr == "1+Z-2*M":
Z = kparam["Z"]
M = kparam["M"]
return 1.0 + Z - 2.0 * M
if expr == "X-2*D":
X = kparam["X"]
D = kparam["D"]
return X - 2 * D
if expr == "(1+a/c*cosbeta)/2/sinbeta/sinbeta":
return (1.0 + a / c * cosbeta) / 2.0 / sinbeta / sinbeta
if expr == "1/2+Y*c*cosbeta/a":
Y = kparam["Y"]
return 1.0 / 2.0 + Y * c * cosbeta / a
if expr == "a*a/4/c/c":
return a * a / 4.0 / c / c
if expr == "5/6-2*D":
D = kparam["D"]
return 5.0 / 6.0 - 2.0 * D
if expr == "1/3+D":
D = kparam["D"]
return 1.0 / 3.0 + D
if expr == "1/6-c*c/9/a/a":
return 1.0 / 6.0 - c * c / 9.0 / a / a
if expr == "1/2-2*Z":
Z = kparam["Z"]
return 1.0 / 2.0 - 2.0 * Z
if expr == "1/2+Z":
Z = kparam["Z"]
return 1.0 / 2.0 + Z
if expr == "(1+b*b/c/c)/4":
return (1.0 + b * b / c / c) / 4.0
if expr == "(1+c*c/b/b)/4":
return (1.0 + c * c / b / b) / 4.0
if expr == "(1+b*b/a/a)/4":
return (1.0 + b * b / a / a) / 4.0
if expr == "(1+a*a/b/b-a*a/c/c)/4":
return (1.0 + a * a / b / b - a * a / c / c) / 4.0
if expr == "(1+a*a/b/b+a*a/c/c)/4":
return (1.0 + a * a / b / b + a * a / c / c) / 4.0
if expr == "(1+c*c/a/a-c*c/b/b)/4":
return (1.0 + c * c / a / a - c * c / b / b) / 4.0
if expr == "(1+c*c/a/a+c*c/b/b)/4":
return (1.0 + c * c / a / a + c * c / b / b) / 4.0
if expr == "(1+b*b/a/a-b*b/c/c)/4":
return (1.0 + b * b / a / a - b * b / c / c) / 4.0
if expr == "(1+c*c/b/b-c*c/a/a)/4":
return (1.0 + c * c / b / b - c * c / a / a) / 4.0
if expr == "(1+a*a/c/c)/4":
return (1.0 + a * a / c / c) / 4.0
if expr == "(b*b-a*a)/4/c/c":
return (b * b - a * a) / 4.0 / c / c
if expr == "(a*a+b*b)/4/c/c":
return (a * a + b * b) / 4.0 / c / c
if expr == "(1+c*c/a/a)/4":
return (1.0 + c * c / a / a) / 4.0
if expr == "(c*c-b*b)/4/a/a":
return (c * c - b * b) / 4.0 / a / a
if expr == "(b*b+c*c)/4/a/a":
return (b * b + c * c) / 4.0 / a / a
if expr == "(a*a-c*c)/4/b/b":
return (a * a - c * c) / 4.0 / b / b
if expr == "(c*c+a*a)/4/b/b":
return (c * c + a * a) / 4.0 / b / b
if expr == "a*a/2/c/c":
return a * a / 2.0 / c / c
raise ValueError(
"Unknown expression, define a new case:\n"
'        elif expr == "{0}":\n'
"            return {0}".format(expr)
)
except KeyError as exc:
raise ValueError(
"Asking for evaluation of symbol '{}' but this has "
"not been defined or not yet computed".format(str(exc))
)

[docs]def check_spglib_version():
"""
Check the SPGLIB version and raise a ValueError if the version is
older than 1.9.4.

Also raises an warning if the user has a version of SPGLIB that is
older than 1.13, because before then there were some bugs (e.g.
wrong treatment of oI, see e.g. issue )

Return the spglib module.
"""
try:
import spglib
except ImportError:
raise ValueError(
"spglib >= 1.9.4 is required for the creation "
"of the k-paths, but it could not be imported"
)

try:
version = spglib.__version__
except NameError:
version = "1.8.0"  # or older, version was introduced only recently

try:
version_pieces = [int(_) for _ in version.split(".")]
if len(version_pieces) < 3:
raise ValueError
except ValueError:
raise ValueError("Unable to parse version number")

if tuple(version_pieces[:2]) < (1, 9):
raise ValueError("Invalid spglib version, need >= 1.9.4")

if version_pieces[:2] == (1, 9) and version_pieces[2] < 4:
raise ValueError("Invalid spglib version, need >= 1.9.4")

if tuple(version_pieces[:2]) < (1, 13):
import warnings

warnings.warn(
"You have a version of SPGLIB older than 1.13, "
"have been fixed",
RuntimeWarning,
)

return spglib

[docs]def get_cell_params(cell):
r"""
Return (a,b,c,cosalpha,cosbeta,cosgamma) given a :math:3\times 3 cell

.. note:: Rows are vectors: v1 = cell[0], v2 = cell[1], v3 = cell[3]
"""
v1, v2, v3 = numpy.array(cell)
a = sqrt(sum(v1 ** 2))
b = sqrt(sum(v2 ** 2))
c = sqrt(sum(v3 ** 2))
cosalpha = numpy.dot(v2, v3) / b / c
cosbeta = numpy.dot(v1, v3) / a / c
cosgamma = numpy.dot(v1, v2) / a / b

return (a, b, c, cosalpha, cosbeta, cosgamma)

[docs]def get_reciprocal_cell_rows(real_space_cell):
r"""
Given the cell in real space (3x3 matrix, vectors as rows,
return the reciprocal-space cell where again the G vectors are
rows, i.e. satisfying
dot(real_space_cell, reciprocal_space_cell.T) = :math:2 \pi I,
where :math:I is the :math:3\times 3 identity matrix.

:return: the :math:3\times 3 list of reciprocal lattice vectors where each row is
one vector.
"""
reciprocal_space_columns = 2.0 * numpy.pi * numpy.linalg.inv(real_space_cell)
return (reciprocal_space_columns.T).tolist()

[docs]def get_real_cell_from_reciprocal_rows(reciprocal_space_rows):
r"""
Given the cell in reciprocal space (3x3 matrix, G vectors as rows,
return the real-space cell where again the R vectors are
rows, i.e. satisfying
dot(real_space_cell, reciprocal_space_cell.T) = :math:2 \pi I,
where :math:I is the :math:3\times 3 identity matrix.

.. note::  This is actually the same as :py:func:get_reciprocal_cell_rows.

:return: the :math:3\times 3 list of real lattice vectors where each row is
one vector.
"""
real_space_columns = 2.0 * numpy.pi * numpy.linalg.inv(reciprocal_space_rows)
return (real_space_columns.T).tolist()

[docs]def get_path_data(ext_bravais):
"""
Given an extended Bravais symbol among those defined in the HPKOT paper
(only first three characters, like cF1), return the points and the
suggested path.

:param ext_bravais: a string among the allowed etended Bravais lattices
defined in HPKOT.
:return: a tuple (kparam_def, points_def, path) where the
first element is the list with the definition of the
k-point parameters, the second is the dictionary with the
definition of the k-points, and the third is the list
with the suggested paths.

.. note:: kparam_def has to be a list and not a dictionary
because the order matters (later k-parameters can be defined
in terms of previous ones)
"""
import os

# Get the data from the band_data folder
this_folder = os.path.split(os.path.abspath(__file__))[0]
folder = os.path.join(this_folder, "band_path_data", ext_bravais)
path_file = os.path.join(folder, "path.txt")
points_file = os.path.join(folder, "points.txt")
kparam_file = os.path.join(folder, "k_vector_parameters.txt")
with open(kparam_file) as f:
kparam_raw = [_.split() for _ in f.readlines() if _.strip()]
with open(points_file) as f:
points_raw = [_.split() for _ in f.readlines()]
with open(path_file) as f:
path_raw = [_.split() for _ in f.readlines()]

# check
if any(len(_) != 2 for _ in kparam_raw):
raise ValueError("Invalid line length in {}".format(kparam_file))
if any(len(_) != 2 for _ in path_raw):
raise ValueError("Invalid line length in {}".format(path_file))
if any(len(_) != 4 for _ in points_raw):
raise ValueError("Invalid line length in {}".format(points_file))
# order must be preserved here
kparam_def = [(_[0], _[1].strip()) for _ in kparam_raw]
points_def = {}
for label, kPx, kPy, kPz in points_raw:
if label in points_def:
raise ValueError(
"Internal error! Point {} defined multiple times "
"for Bravais lattice {}".format(label, ext_bravais)
)
points_def[label] = (kPx, kPy, kPz)
path = [(_[0], _[1]) for _ in path_raw]

# check path is valid
for p1, p2 in path:
if p1 not in points_def:
raise ValueError(
"Point {} found in path (for {}) but undefined!".format(p1, ext_bravais)
)
if p2 not in points_def:
raise ValueError(
"Point {} found in path (for {}) but undefined!".format(p2, ext_bravais)
)

return (kparam_def, points_def, path)