Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate buggy legacy implementation of kpoint generation in KpointsData #1015

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
570 changes: 513 additions & 57 deletions aiida/backends/tests/dataclasses.py

Large diffs are not rendered by default.

2,040 changes: 183 additions & 1,857 deletions aiida/orm/data/array/kpoints.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions aiida/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@


from aiida.tools.dbimporters import DbImporterFactory
from aiida.tools.data.array.kpoints import get_kpoints_path, get_explicit_kpoints_path
from aiida.tools.data.structure import structure_to_spglib_tuple, spglib_tuple_to_structure
10 changes: 10 additions & 0 deletions aiida/tools/data/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved. #
# This file is part of the AiiDA code. #
# #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core #
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################

9 changes: 9 additions & 0 deletions aiida/tools/data/array/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved. #
# This file is part of the AiiDA code. #
# #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core #
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################
252 changes: 252 additions & 0 deletions aiida/tools/data/array/kpoints/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved. #
# This file is part of the AiiDA code. #
# #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core #
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################
from aiida.orm.data.array.kpoints import KpointsData
from aiida.orm.data.parameter import ParameterData
from aiida.tools.data.array.kpoints import legacy
from aiida.tools.data.array.kpoints import seekpath

__all__ = ['get_kpoints_path', 'get_explicit_kpoints_path']


def get_kpoints_path(structure, method='seekpath', **kwargs):
"""
Returns a dictionary whose contents depend on the method but includes at least the following keys

* parameters: ParameterData node

The contents of the parameters depends on the method but contains at least the keys

* 'point_coords': a dictionary with 'kpoints-label': [float coordinates]
* 'path': a list of length-2 tuples, with the labels of the starting
and ending point of each label section

The 'seekpath' method which is the default also returns the following additional nodes

* primitive_structure: StructureData with the primitive cell
* conv_structure: StructureData with the conventional cell

Note that the generated kpoints for the seekpath method only apply on the returned primitive_structure
and not on the input structure that was provided

:param structure: a StructureData node
:param method: the method to use for kpoint generation, options are 'seekpath' and 'legacy'.
It is strongly advised to use the default 'seekpath' as the 'legacy' implementation is known to have
bugs for certain structure cells
:param kwargs: optional keyword arguments that depend on the selected method
:returns: dictionary as described above in the docstring
"""
if method not in _get_kpoints_path_methods.keys():
raise ValueError("the method '{}' is not implemented".format(method))

if method == 'seekpath':
try:
seekpath.check_seekpath_is_installed()
except ImportError as exception:
raise ValueError("selected method is 'seekpath' but the package is not installed\n"
"Either install it or pass method='legacy' as input to the function call")

method = _get_kpoints_path_methods[method]

return method(structure, **kwargs)


def get_explicit_kpoints_path(structure, method='seekpath', **kwargs):
"""
Returns a dictionary whose contents depend on the method but includes at least the following keys

* parameters: ParameterData node
* explicit_kpoints: KpointsData node with explicit kpoints path

The contents of the parameters depends on the method but contains at least the keys

* 'point_coords': a dictionary with 'kpoints-label': [float coordinates]
* 'path': a list of length-2 tuples, with the labels of the starting
and ending point of each label section

The 'seekpath' method which is the default also returns the following additional nodes

* primitive_structure: StructureData with the primitive cell
* conv_structure: StructureData with the conventional cell

Note that the generated kpoints for the seekpath method only apply on the returned primitive_structure
and not on the input structure that was provided

:param structure: a StructureData node
:param method: the method to use for kpoint generation, options are 'seekpath' and 'legacy'.
It is strongly advised to use the default 'seekpath' as the 'legacy' implementation is known to have
bugs for certain structure cells
:param kwargs: optional keyword arguments that depend on the selected method
:returns: dictionary as described above in the docstring
"""
if method not in _get_explicit_kpoints_path_methods.keys():
raise ValueError("the method '{}' is not implemented".format(method))

if method == 'seekpath':
try:
seekpath.check_seekpath_is_installed()
except ImportError as exception:
raise ValueError("selected method is 'seekpath' but the package is not installed\n"
"Either install it or pass method='legacy' as input to the function call")

method = _get_explicit_kpoints_path_methods[method]

return method(structure, **kwargs)


def _seekpath_get_kpoints_path(structure, **kwargs):
"""
Call the get_kpoints_path wrapper function for Seekpath

:param structure: a StructureData node
:param with_time_reversal: if False, and the group has no inversion
symmetry, additional lines are returned
: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
"""
assert structure.pbc == (True, True, True), 'Seekpath only implemented for three-dimensional structures'

recognized_args = ['with_time_reversal', 'recipe', 'threshold', 'symprec', 'angle_tolerance']
unknown_args = set(kwargs).difference(recognized_args)

if unknown_args:
raise ValueError("unknown arguments {}".format(unknown_args))

return seekpath.get_kpoints_path(structure, kwargs)


def _seekpath_get_explicit_kpoints_path(structure, **kwargs):
"""
Call the get_explicit_kpoints_path wrapper function for Seekpath

:param structure: a StructureData node
:param with_time_reversal: if False, and the group has no inversion
symmetry, additional lines are returned
: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
"""
assert structure.pbc == (True, True, True), 'Seekpath only implemented for three-dimensional structures'

recognized_args = ['with_time_reversal', 'reference_distance', 'recipe', 'threshold', 'symprec', 'angle_tolerance']
unknown_args = set(kwargs).difference(recognized_args)

if unknown_args:
raise ValueError("unknown arguments {}".format(unknown_args))

return seekpath.get_explicit_kpoints_path(structure, kwargs)


def _legacy_get_kpoints_path(structure, **kwargs):
"""
Call the get_kpoints_path of the legacy implementation

:param structure: a StructureData node
:param bool cartesian: if set to true, reads the coordinates eventually passed in value as cartesian coordinates
:param epsilon_length: threshold on lengths comparison, used to get the bravais lattice info
:param epsilon_angle: threshold on angles comparison, used to get the bravais lattice info
"""
args_recognized = ['cartesian', 'epsilon_length', 'epsilon_angle']
args_unknown = set(kwargs).difference(args_recognized)

if args_unknown:
raise ValueError("unknown arguments {}".format(args_unknown))

point_coords, path, bravais_info = legacy.get_kpoints_path(
cell=structure.cell, pbc=structure.pbc, **kwargs
)

parameters = {
'bravais_info': bravais_info,
'point_coords': point_coords,
'path': path,
}

return {'parameters': ParameterData(dict=parameters)}


def _legacy_get_explicit_kpoints_path(structure, **kwargs):
"""
Call the get_explicit_kpoints_path of the legacy implementation

:param structure: a StructureData node
:param float kpoint_distance: parameter controlling the distance between kpoints. Distance is
given in crystal coordinates, i.e. the distance is computed in the space of b1, b2, b3.
The distance set will be the closest possible to this value, compatible with the requirement
of putting equispaced points between two special points (since extrema are included).
:param bool cartesian: if set to true, reads the coordinates eventually passed in value as cartesian coordinates
:param float epsilon_length: threshold on lengths comparison, used to get the bravais lattice info
:param float epsilon_angle: threshold on angles comparison, used to get the bravais lattice info
"""
args_recognized = ['value', 'kpoint_distance', 'cartesian', 'epsilon_length', 'epsilon_angle']
args_unknown = set(kwargs).difference(args_recognized)

if args_unknown:
raise ValueError("unknown arguments {}".format(args_unknown))

point_coords, path, bravais_info, explicit_kpoints, labels = legacy.get_explicit_kpoints_path(
cell=structure.cell, pbc=structure.pbc, **kwargs
)

kpoints = KpointsData()
kpoints.set_cell(structure.cell)
kpoints.set_kpoints(explicit_kpoints)
kpoints.labels = labels

parameters = {
'bravais_info': bravais_info,
'point_coords': point_coords,
'path': path,
}

return {
'parameters': ParameterData(dict=parameters),
'explicit_kpoints': kpoints
}



_get_kpoints_path_methods = {
'legacy': _legacy_get_kpoints_path,
'seekpath': _seekpath_get_kpoints_path,
}

_get_explicit_kpoints_path_methods = {
'legacy': _legacy_get_explicit_kpoints_path,
'seekpath': _seekpath_get_explicit_kpoints_path,
}
Loading