# Source code for seekpath.getpaths

"""
This module contains the main functions to get a path and an explicit path.
"""
import numpy as np

[docs]def get_explicit_from_implicit(  # pylint: disable=too-many-locals
seekpath_output, reference_distance
):
"""
Given the output of get_path by seekpath, compute an "explicit" path,
i.e. instead of just giving the endpoints and their coordinates, compute
a full list of kpoints

:param seekpath_output: a dictionary, the output of seekpath.get_path

:param reference_distance: a reference target distance between neighboring
k-points in the path, in units of 1/ang. The actual value will be as
close as possible to this value, to have an integer number of points in
each path.
"""
retdict = {}

kpoints_rel = []
kpoints_labels = []
kpoints_linearcoord = []
previous_linearcoord = 0.0
segments = []
for start_label, stop_label in seekpath_output["path"]:
start_coord = np.array(seekpath_output["point_coords"][start_label])
stop_coord = np.array(seekpath_output["point_coords"][stop_label])
start_coord_abs = np.dot(
start_coord, seekpath_output["reciprocal_primitive_lattice"]
)
stop_coord_abs = np.dot(
stop_coord, seekpath_output["reciprocal_primitive_lattice"]
)
segment_length = np.linalg.norm(stop_coord_abs - start_coord_abs)
num_points = max(2, int(segment_length / reference_distance))
segment_linearcoord = np.linspace(0.0, segment_length, num_points)
segment_start = len(kpoints_labels)
for i in range(num_points):
# Skip the first point if it's the same as the last one of
# the previous segment
if i == 0:
if kpoints_labels:
if kpoints_labels[-1] == start_label:
segment_start -= 1
continue

kpoints_rel.append(
start_coord
+ (stop_coord - start_coord) * float(i) / float(num_points - 1)
)
if i == 0:
kpoints_labels.append(start_label)
elif i == num_points - 1:
kpoints_labels.append(stop_label)
else:
kpoints_labels.append("")
kpoints_linearcoord.append(previous_linearcoord + segment_linearcoord[i])
previous_linearcoord += segment_length
segment_end = len(kpoints_labels)
segments.append((segment_start, segment_end))

retdict["kpoints_rel"] = np.array(kpoints_rel)
retdict["kpoints_linearcoord"] = np.array(kpoints_linearcoord)
retdict["kpoints_labels"] = kpoints_labels
retdict["kpoints_abs"] = np.dot(
retdict["kpoints_rel"], seekpath_output["reciprocal_primitive_lattice"]
)
retdict["segments"] = segments

return retdict

[docs]def get_path(
structure,
with_time_reversal=True,
recipe="hpkot",
threshold=1.0e-7,
symprec=1e-05,
angle_tolerance=-1.0,
):
r"""
Return the kpoint path information for band structure given a
crystal structure, using the paths from the chosen recipe/reference.

If you use this module, please cite the paper of the corresponding
recipe (see parameter below).

:param structure: The crystal structure for which we want to obtain
the suggested path. It should be a tuple in the format
accepted by spglib: (cell, positions, numbers), where
(if N is the number of atoms):

- cell is a :math:3 \times 3 list of floats (cell[0] is the first lattice
vector, ...)
- positions is a :math:N \times 3 list of floats with the atomic coordinates
in scaled coordinates (i.e., w.r.t. the cell vectors)
- numbers is a length-:math:N list with integers identifying uniquely
the atoms in the cell (e.g., the Z number of the atom, but
any other positive non-zero integer will work - e.g. if you
want to distinguish two Carbon atoms, you can set one number
to 6 and the other to 1006)

:param with_time_reversal: if False, and the group has no inversion
symmetry, additional lines are returned as described in the HPKOT
paper.

:param recipe: choose the reference publication that defines the special
points and paths.
Currently, the following value is implemented:

- hpkot: HPKOT paper:
Y. Hinuma, G. Pizzi, Y. Kumagai, F. Oba, I. Tanaka, Band structure
diagram paths based on crystallography, Comp. Mat. Sci. 128, 140 (2017).
DOI: 10.1016/j.commatsci.2016.10.015

:param threshold: the threshold to use to verify if we are in
and edge case (e.g., a tetragonal cell, but a==c). For instance,
in the tI lattice, if abs(a-c) < threshold, a
:py:exc:~seekpath.hpkot.EdgeCaseWarning is issued.
Note that depending on the bravais lattice, the meaning of the
threshold is different (angle, length, ...)

:param symprec: the symmetry precision used internally by SPGLIB

:param angle_tolerance: the angle_tolerance used internally by SPGLIB

:return: a dictionary with the following
keys:

- point_coords: a dictionary with label -> float coordinates
- path: a list of length-2 tuples, with the labels of the starting
and ending point of each label section
- has_inversion_symmetry: True or False, depending on whether the
input crystal structure has inversion symmetry or not.
- augmented_path: if True, it means that the path was
augmented with the :math:-k points (this happens if both
has_inversion_symmetry is False, and the user set
with_time_reversal=False in the input)
- bravais_lattice: the Bravais lattice string (like cP, tI, ...)
- bravais_lattice_extended: the specific case used to define labels and
coordinates (like cP1, tI2, ...)
- cont_lattice: three real-space vectors for the crystallographic
conventional cell (conv_lattice[0,:] is the first vector)
- conv_positions: fractional coordinates of atoms in the
crystallographic conventional cell
- conv_types: list of integer types of the atoms in the crystallographic
conventional cell (typically, the atomic numbers)
- primitive_lattice: three real-space vectors for the crystallographic
primitive cell (primitive_lattice[0,:] is the first vector)
- primitive_positions: fractional coordinates of atoms in the
crystallographic primitive cell
- primitive_types: list of integer types of the atoms in the
crystallographic primitive cell (typically, the atomic numbers)
- reciprocal_primitive_lattice: reciprocal-cell vectors for the
primitive cell (vectors are rows: reciprocal_primitive_lattice[0,:]
is the first vector)
- primitive_transformation_matrix: the transformation matrix :math:P between
the conventional and the primitive cell
- inverse_primitive_transformation_matrix: the inverse of the matrix :math:P
(the determinant is integer and gives the ratio in volume between
the conventional and primitive cells)
- volume_original_wrt_conv: volume ratio of the user-provided cell
with respect to the the crystallographic conventional cell
- volume_original_wrt_prim: volume ratio of the user-provided cell
with respect to the the crystalloraphic primitive cell

:note: An :py:exc:~seekpath.hpkot.EdgeCaseWarning is issued for
edge cases (e.g. if a==b==c for
orthorhombic systems). In this case, still one of the valid cases
is picked.
"""
if recipe == "hpkot":
from . import hpkot

res = hpkot.get_path(
structure=structure,
with_time_reversal=with_time_reversal,
threshold=threshold,
symprec=symprec,
angle_tolerance=angle_tolerance,
)

else:
raise ValueError(
"value for 'recipe' not recognized. The only value "
"currently accepted is 'hpkot'."
)
return res

[docs]def get_explicit_k_path(
structure,
with_time_reversal=True,
reference_distance=0.025,
recipe="hpkot",
threshold=1.0e-7,
symprec=1e-05,
angle_tolerance=-1.0,
):
r"""
Return the kpoint path for band structure (in scaled and absolute
coordinates), given a crystal structure,
using the paths proposed in the various publications (see description
of the 'recipe' input parameter).
Note that the k-path typically refers to a different unit cell
(e.g., the primitive one with some transformation matrix to comply with
the conventions in the case of the HPKOT paper). So, one should use the
crystal cell provided in output for all calculations, rather than the
input 'structure'.

If you use this module, please cite the paper of the corresponding
recipe (see parameter below).

:param structure: The crystal structure for which we want to obtain
the suggested path. It should be a tuple in the format
accepted by spglib: (cell, positions, numbers), where
(if N is the number of atoms):

- cell is a :math:3 \times 3 list of floats (cell[0] is the first lattice
vector, ...)
- positions is a :math:N \times 3 list of floats with the atomic coordinates
in scaled coordinates (i.e., w.r.t. the cell vectors)
- numbers is a length-:math:N list with integers identifying uniquely
the atoms in the cell (e.g., the Z number of the atom, but
any other positive non-zero integer will work - e.g. if you
want to distinguish two Carbon atoms, you can set one number
to 6 and the other to 1006)

:param with_time_reversal: if False, and the group has no inversion

:param reference_distance: a reference target distance between neighboring
k-points in the path, in units of 1/ang. The actual value will be as
close as possible to this value, to have an integer number of points in
each path.

:param recipe: choose the reference publication that defines the special
points and paths.
Currently, the following value is implemented:

- hpkot: HPKOT paper:
Y. Hinuma, G. Pizzi, Y. Kumagai, F. Oba, I. Tanaka, Band structure
diagram paths based on crystallography, Comp. Mat. Sci. 128, 140 (2017).
DOI: 10.1016/j.commatsci.2016.10.015

:param threshold: the threshold to use to verify if we are in
and edge case (e.g., a tetragonal cell, but a==c). For instance,
in the tI lattice, if abs(a-c) < threshold, a
:py:exc:~seekpath.hpkot.EdgeCaseWarning is issued.
Note that depending on the bravais lattice, the meaning of the
threshold is different (angle, length, ...)

:param symprec: the symmetry precision used internally by SPGLIB

:param angle_tolerance: the angle_tolerance used internally by SPGLIB

.. versionchanged:: 1.8
The key segments has been renamed explicit_segments
for consistency.

:return: a dictionary with a number of keys. They are the same as
get_path, plus a few ones:

- explicit_kpoints_abs: List of the kpoints along the specific path in
absolute (Cartesian) coordinates. The two endpoints are always
included, independently of the length.
- explicit_kpoints_rel: List of the kpoints along the specific path in
relative (fractional) coordinates (same length as
explicit_kpoints_abs).
- explicit_kpoints_labels: list of strings with kpoints labels. It has
the same length as explicit_kpoints_abs and explicit_kpoints_rel.
Empty if the point is not a special point.
- explicit_kpoints_linearcoord: array of floats, giving the coordinate
at which to plot the corresponding point.
- explicit_segments: a list of length-2 tuples, with the start and end
index of each segment. **Note**! The indices are supposed to be
used as follows: the labels for the i-th segment are given by::

segment_indices = explicit_segments[i]
segment_labels = explicit_kpoints_labels[slice(*segment_indices)]

This means, in particular, that if you want the label of the start
and end points, you should do::

start_label = explicit_kpoints_labels[segment_indices[0]]
stop_label = explicit_kpoints_labels[segment_indices[1]-1]

(note the minus one!)

Also, note that if
explicit_segments[i-1][1] == explicit_segments[i][0] + 1 it means
that the point was calculated only once, and it belongs to both
explicit_segments[i-1][1] == explicit_segments[i][0], then
this is a 'break' point in the path (e.g., explicit_segments[i-1][1]
is the X point, and explicit_segments[i][0] is the R point,
and typically in a graphical representation they are shown at the
same coordinate, with a label R|X).
"""
if recipe == "hpkot":
from . import hpkot

res = hpkot.get_path(
structure=structure,
with_time_reversal=with_time_reversal,
threshold=threshold,
symprec=symprec,
angle_tolerance=angle_tolerance,
)

else:
raise ValueError(
"value for 'recipe' not recognized. The only value "
"currently accepted is 'hpkot'."
)

explicit_res = get_explicit_from_implicit(
res, reference_distance=reference_distance
)
for k, v in explicit_res.items():
res["explicit_{}".format(k)] = v
return res