#!/usr/bin/env python
# -*- coding: utf-8 -*-
r"""
A collection of functions for creating k-points input for different
first-principles codes.
All functions have the same calling structure as :func:`prototype`.
"""
import decorator
import numpy as np
from fsc.export import export
[docs]@export
def prototype(kpt):
r"""
Specifies the interface
:param kpt: The list of k-points in the string INCLUDING the
final point which should not be in the calculation
:type kpt: :py:class:`list` of :py:obj:`numpy.array`
"""
raise NotImplementedError('This is only the prototype for kpts')
@decorator.decorator
def _check_dim(fct, kpt):
"""Checks if all k-points are three-dimensional."""
for k in kpt:
if len(k) != 3:
raise ValueError('Dimension of point k = {} != 3'.format(k))
return fct(kpt)
@decorator.decorator
def _check_closed(fct, kpt):
"""Checks whether the k-point list forms a closed loop."""
delta = kpt[-1] - kpt[0]
if not np.isclose(np.round_(delta), delta).all():
raise ValueError('The k-point line does not form a closed loop.')
return fct(kpt)
[docs]@export
@_check_dim
@_check_closed
def abinit(kpt):
"""
Creates a k-point input for **ABINIT**. It uses ``kptopt -1`` and specifies the k-points string using ``ndivk`` and ``kptbounds``.
"""
_check_equal_spacing(kpt, 'ABINIT')
start_point = kpt[0]
last_point = kpt[-2]
num_kpt = len(kpt) - 1
string = "\nkptopt -1\nndivk " + str(int(num_kpt - 1)) + '\nkptbounds '
for coord in start_point:
string += str(coord).replace('e', 'd') + ' '
string += '\n'
for coord in last_point:
string += str(coord).replace('e', 'd') + ' '
string += '\n'
return string
[docs]@export
@_check_dim
@_check_closed
def qe(kpt): # pylint: disable=invalid-name
"""
Creates a k-point input for **Quantum Espresso**.
"""
start_point = kpt[0]
last_point = kpt[-2]
num_kpt = len(kpt) - 1
string = "\nK_POINTS crystal_b\n 2 \n"
for coord in start_point:
string += str(coord).replace('e', 'd') + ' '
string += str(num_kpt - 1) + '\n'
for coord in last_point:
string += str(coord).replace('e', 'd') + ' '
string += str(1) + '\n'
return string
[docs]@export
@_check_dim
@_check_closed
def qe_explicit(kpt):
"""
Creates a k-point input for **Quantum Espresso**, by explicitly specifying the k-points.
"""
num_kpt = len(kpt) - 1
string = "\nK_POINTS crystal\n {} \n".format(num_kpt)
kpt_str = ((str(coord).replace('e', 'd') for coord in k) for k in kpt)
for k in kpt_str:
string += '{} {} {} 1\n'.format(*k)
return string
[docs]@export
@_check_dim
@_check_closed
def wannier90(kpt):
"""
Creates a k-point input for **Wannier90**. It can be useful when the first-principles code does not generate the k-points in ``wannier90.win`` (e.g. with Quantum Espresso).
"""
num_kpt = len(kpt) - 1
string = "mp_grid: " + str(int(num_kpt)) + " 1 1 \nbegin kpoints"
for k in kpt[:-1]:
string += '\n'
for coord in k:
string += str(coord).replace('e', 'd') + ' '
string += '\nend kpoints\n'
return string
[docs]@export
@_check_dim
@_check_closed
def wannier90_nnkpts(kpt):
"""
Creates the nnkpts input to explicitly specify the nearest neighbours in wannier90.win
"""
num_kpt = len(kpt) - 1
bz_diff = [np.zeros(3, dtype=int) for _ in range(num_kpt - 1)]
# check whether the last k-point is in a different UC
bz_diff.append(np.array(np.round_(kpt[-1] - kpt[0]), dtype=int))
string = 'begin nnkpts\n'
for i, k in enumerate(bz_diff):
j = (i + 1) % num_kpt
string += ' {0:>3} {1:>3} {2[0]: } {2[1]: } {2[2]: }\n'.format(
i + 1, j + 1, k
)
string += 'end nnkpts\n'
return string
[docs]@export
@_check_dim
@_check_closed
def wannier90_full(kpt):
"""
Returns both k-point and nearest neighbour input for wannier90.win. This is the recommended function to use for Wannier90 2.1 and higher.
"""
return wannier90(kpt) + '\n' + wannier90_nnkpts(kpt)
[docs]@export
@_check_dim
@_check_closed
def vasp(kpt):
"""
Creates a k-point input for **VASP**. It uses the automatic generation scheme with a Gamma centered grid. Note that VASP does **not** support any kind of k-point line **unless** they are exactly along one of the reciprocal lattice vectors, and the k-points are evenly spaced.
"""
# VALIDITY CHECKS
# check if the points are equally-spaced
delta = _check_equal_spacing(kpt, 'VASP')
num_kpt = len(kpt) - 1
# check if it's positive x, y or z direction
nonzero = []
mesh = []
for i, spacing in enumerate(delta):
if np.isclose(spacing, 0):
mesh.append('1')
elif np.isclose(spacing, 1 / num_kpt):
nonzero.append(i)
mesh.append(str(num_kpt))
else:
raise ValueError(
'The k-points must be aligned in (positive) kx-, ky- or kz-direction for VASP runs.'
)
mesh_str = ' '.join(mesh)
if len(nonzero) != 1:
raise ValueError(
'The k-points can change only in kx-, ky- or kz direction for VASP runs. The given k-points change in {} directions.'
.format(len(nonzero))
)
start_point = kpt[0]
if not np.isclose(start_point[nonzero[0]], 0):
raise ValueError(
'The k-points must start at k{0} = 0 for VASP runs, since they change in k{0}-direction.'
.format(['x', 'y', 'z'][nonzero[0]])
)
string = 'Automatic mesh\n0 ! number of k-points = 0 ->automatic generation scheme\nGamma ! generate a Gamma centered grid\n'
string += mesh_str + ' ! subdivisions\n'
for coord in start_point:
string += str(coord).replace('e', 'd') + ' '
string += ' ! shift\n'
return string
def _check_equal_spacing(kpt, run_type):
"""Checks if the k-points are equally spaced, and throws an error if not. run_type is added in the error message."""
deltas = [(k2 - k1) % 1 for k2, k1 in zip(kpt[1:], kpt[:-1])]
for spacing in deltas[1:]:
if not np.isclose(spacing, deltas[0]).all():
raise ValueError(
'The k-points must be equally spaced for {} runs.'.
format(run_type)
)
return deltas[0]