Commit b81635c9 authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Starting to migrate band structure related normalization to a separate normalizer.

parent fe414f39
......@@ -2,7 +2,7 @@ import numpy as np # pylint: disable=unused-import
import typing # pylint: disable=unused-import
from nomad.metainfo import ( # pylint: disable=unused-import
MSection, MCategory, Category, Package, Quantity, Section, SubSection, SectionProxy,
Reference
Reference, MEnum
)
from nomad.metainfo.legacy import LegacyDefinition
......@@ -2025,111 +2025,6 @@ class section_gaussian_basis_group(MSection):
a_legacy=LegacyDefinition(name='number_of_gaussian_basis_group_exponents'))
class section_k_band_normalized(MSection):
'''
This section stores information on a normalized $k$-band (electronic band structure)
evaluation along one-dimensional pathways in the $k$ (reciprocal) space given in
section_k_band_segment. Eigenvalues calculated at the actual $k$-mesh used for
energy_total evaluations, can be found in the section_eigenvalues section.
'''
m_def = Section(validate=False, a_legacy=LegacyDefinition(name='section_k_band_normalized'))
k_band_path_normalized_is_standard = Quantity(
type=bool,
shape=[],
description='''
If the normalized path is along the default path defined in W. Setyawan and S.
Curtarolo, [Comput. Mater. Sci. **49**, 299-312
(2010)](http://dx.doi.org/10.1016/j.commatsci.2010.05.010).
''',
a_legacy=LegacyDefinition(name='k_band_path_normalized_is_standard'))
section_k_band_segment_normalized = SubSection(
sub_section=SectionProxy('section_k_band_segment_normalized'),
repeats=True,
a_legacy=LegacyDefinition(name='section_k_band_segment_normalized'))
class section_k_band_segment_normalized(MSection):
'''
Section collecting the information on a normalized $k$-band segment. This section
stores band structures along a one-dimensional pathway in the $k$ (reciprocal) space.
Eigenvalues calculated at the actual $k$-mesh used for energy_total evaluations are
defined in section_eigenvalues and the band structures are represented as third-order
tensors: one dimension for the spin channels, one for the sequence of $k$ points for
the segment (given in number_of_k_points_per_segment), and one for the sequence of
eigenvalues at a given $k$ point. The values of the $k$ points in each segment are
stored in band_k_points. The energies and occupation for each eigenstate, at each $k$
point, segment, and spin channel are stored in band_energies and band_occupations,
respectively. The labels for the segment are specified in band_segm_labels.
'''
m_def = Section(validate=False, a_legacy=LegacyDefinition(name='section_k_band_segment_normalized'))
band_energies_normalized = Quantity(
type=np.dtype(np.float64),
shape=['number_of_spin_channels', 'number_of_normalized_k_points_per_segment', 'number_of_normalized_band_segment_eigenvalues'],
unit='joule',
description='''
$k$-dependent energies of the electronic band segment (electronic band structure)
with respect to the top of the valence band. This is a third-order tensor, with
one dimension used for the spin channels, one for the $k$ points for each segment,
and one for the eigenvalue sequence.
''',
a_legacy=LegacyDefinition(name='band_energies_normalized'))
band_k_points_normalized = Quantity(
type=np.dtype(np.float64),
shape=['number_of_normalized_k_points_per_segment', 3],
description='''
Fractional coordinates of the $k$ points (in the basis of the reciprocal-lattice
vectors) for which the normalized electronic energies are given.
''',
a_legacy=LegacyDefinition(name='band_k_points_normalized'))
band_occupations_normalized = Quantity(
type=np.dtype(np.float64),
shape=['number_of_spin_channels', 'number_of_normalized_k_points_per_segment', 'number_of_normalized_band_segment_eigenvalues'],
description='''
Occupation of the $k$-points along the normalized electronic band. The size of the
dimensions of this third-order tensor are the same as for the tensor in
band_energies.
''',
a_legacy=LegacyDefinition(name='band_occupations_normalized'))
band_segm_labels_normalized = Quantity(
type=str,
shape=[2],
description='''
Start and end labels of the points in the segment (one-dimensional pathways)
sampled in the $k$-space, using the conventional symbols, e.g., Gamma, K, L. The
coordinates (fractional, in the reciprocal space) of the start and end points for
each segment are given in band_segm_start_end_normalized
''',
a_legacy=LegacyDefinition(name='band_segm_labels_normalized'))
band_segm_start_end_normalized = Quantity(
type=np.dtype(np.float64),
shape=[2, 3],
description='''
Fractional coordinates of the start and end point (in the basis of the reciprocal
lattice vectors) of the segment sampled in the $k$ space. The conventional symbols
(e.g., Gamma, K, L) of the same points are given in band_segm_labels
''',
a_legacy=LegacyDefinition(name='band_segm_start_end_normalized'))
number_of_normalized_k_points_per_segment = Quantity(
type=int,
shape=[],
description='''
Gives the number of $k$ points in the segment of the normalized band structure
(see section_k_band_segment_normalized).
''',
a_legacy=LegacyDefinition(name='number_of_normalized_k_points_per_segment'))
class section_k_band_segment(MSection):
'''
Section collecting the information on a $k$-band or $q$-band segment. This section
......@@ -2211,6 +2106,63 @@ class section_k_band_segment(MSection):
a_legacy=LegacyDefinition(name='number_of_k_points_per_segment'))
class section_band_gap(MSection):
'''
This section stores information for a band gap within a band structure.
'''
m_def = Section(validate=False, a_legacy=LegacyDefinition(name='section_band_gap'))
value = Quantity(
type=float,
unit="joule",
description="""
Band gap energy.
""",
a_legacy=LegacyDefinition(name='value')
)
type = Quantity(
type=MEnum("direct", "indirect"),
description="""
Type of band gap.
""",
a_legacy=LegacyDefinition(name='type')
)
conduction_band_min_energy = Quantity(
type=float,
unit="joule",
description="""
Conduction band minimum energy.
""",
a_legacy=LegacyDefinition(name='conduction_band_min_energy')
)
valence_band_max_energy = Quantity(
type=float,
unit="joule",
description="""
Valence band maximum energy.
""",
a_legacy=LegacyDefinition(name='valence_band_max_energy')
)
conduction_band_min_k_point = Quantity(
type=np.dtype(np.float64),
shape=[3],
unit="1 / meter",
description="""
Coordinate of the conduction band minimum in k-space.
""",
a_legacy=LegacyDefinition(name='conduction_band_min_k_point')
)
valence_band_max_k_point = Quantity(
type=np.dtype(np.float64),
shape=[3],
unit="1 / meter",
description="""
Coordinate of the valence band minimum in k-space.
""",
a_legacy=LegacyDefinition(name='valence_band_max_k_point')
)
class section_k_band(MSection):
'''
This section stores information on a $k$-band (electronic or vibrational band
......@@ -2229,6 +2181,61 @@ class section_k_band(MSection):
''',
a_legacy=LegacyDefinition(name='band_structure_kind'))
reciprocal_cell = Quantity(
type=np.dtype(np.float64),
shape=[3, 3],
unit="1 / meter",
description="""
The reciprocal cell within which the band structure is calculated.
""",
a_legacy=LegacyDefinition(name='reciprocal_cell')
)
brillouin_zone = Quantity(
type=str,
description="""
The Brillouin zone that corresponds to the reciprocal cell used in the
band calculation. The Brillouin Zone is defined as a list of vertices
and facets that are encoded with JSON. The vertices are 3D points in
the reciprocal space, and facets are determined by a chain of vertice
indices, with a right-hand ordering determining the surface normal
direction.
{
"vertices": [[3, 2, 1], ...]
"faces": [[0, 1, 2, 3], ...]
}
""",
a_legacy=LegacyDefinition(name='brillouin_zone')
)
section_band_gap = SubSection(
sub_section=section_band_gap.m_def,
repeats=False,
a_legacy=LegacyDefinition(name='section_band_gap')
)
section_band_gap_spin_up = SubSection(
sub_section=section_band_gap.m_def,
repeats=False,
a_legacy=LegacyDefinition(name='section_band_gap')
)
section_band_gap_spin_down = SubSection(
sub_section=section_band_gap.m_def,
repeats=False,
a_legacy=LegacyDefinition(name='section_band_gap')
)
is_standard_path = Quantity(
type=bool,
description="""
Boolean indicating whether the path follows the standard path for this
cell. The AFLOW standard by Setyawan and Curtarolo is used
(https://doi.org/10.1016/j.commatsci.2010.05.010).
""",
a_legacy=LegacyDefinition(name='is_standard_path')
)
section_k_band_segment = SubSection(
sub_section=SectionProxy('section_k_band_segment'),
repeats=True,
......
import numpy as np
from elasticsearch_dsl import InnerDoc
from nomad.metainfo import MSection, Section, SubSection, Quantity, MEnum, units
from nomad.metainfo import MSection, Section, SubSection, Quantity, MEnum, Reference
from nomad.datamodel.metainfo.public import section_k_band, section_dos
class WyckoffVariables(MSection):
......@@ -408,199 +409,6 @@ class Calculation(MSection):
)
class BandGap(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
description="""
Stores information related to a band gap that has bee identified within
the band structure.
"""
)
value = Quantity(
type=float,
unit=units.J,
description="""
Band gap energy.
"""
)
type = Quantity(
type=MEnum("direct", "indirect"),
description="""
Type of band gap.
"""
)
conduction_band_min_energy = Quantity(
type=float,
unit=units.J,
description="""
Conduction band minimum energy.
"""
)
valence_band_max_energy = Quantity(
type=float,
unit=units.J,
description="""
Valence band maximum energy.
"""
)
conduction_band_min_k_point = Quantity(
type=np.dtype(np.float64),
shape=[3],
unit=units.m**(-1),
description="""
Coordinate of the conduction band minimum in k-space.
"""
)
valence_band_max_k_point = Quantity(
type=np.dtype(np.float64),
shape=[3],
unit=units.m**(-1),
description="""
Coordinate of the valence band minimum in k-space.
"""
)
class BandSegment(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
description="""
Represents a continuous path segment starting from a specific k-point
and ending in another.
"""
)
energies = Quantity(
type=np.dtype(np.float64),
shape=["1..2", "1..*", "1..*"],
unit=units.m**(-1),
description="""
The energies of the bands as a 3D array with size [n_spin_channels,
n_bands, n_k_points]. By default the spin down channel is given first
and the spin up channel is second.
"""
)
k_points = Quantity(
type=np.dtype(np.float64),
shape=["1..*", 3],
unit=units.m**(-1),
description="""
Path of the band structure in reciprocal space.
"""
)
labels = Quantity(
type=np.dtype('str'),
shape=[1],
description="""
The start end end labels for the k points in this segment.
"""
)
class ElectronicBandStructure(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
description="""
Stores information related to an electronic band structure.
"""
)
scc_index = Quantity(
type=int,
description="""
Index of the single configuration calculation that contains the band
structure.
"""
)
fermi_level = Quantity(
type=float,
unit=units.J,
description="""
Fermi level reported for the band structure.
"""
)
reciprocal_cell = Quantity(
type=np.dtype(np.float64),
shape=[3, 3],
unit=units.m**(-1),
description="""
The reciprocal cell within which the band structure is calculated.
"""
)
brillouin_zone = Quantity(
type=str,
description="""
The Brillouin zone that corresponds to the reciprocal cell used in the
band calculation. The Brillouin Zone is defined as a list of vertices
and facets that are encoded with JSON. The vertices are 3D points in
the reciprocal space, and facets are determined by a chain of vertice
indices, with a right-hand ordering determining the surface normal
direction.
{
"vertices": [[3, 2, 1], ...]
"faces": [[0, 1, 2, 3], ...]
}
"""
)
band_gap = SubSection(sub_section=BandGap.m_def, repeats=False)
band_gap_spin_up = SubSection(sub_section=BandGap.m_def, repeats=False)
band_gap_spin_down = SubSection(sub_section=BandGap.m_def, repeats=False)
segments = SubSection(sub_section=BandSegment.m_def, repeats=True)
is_standard_path = Quantity(
type=bool,
description="""
Boolean indicating whether the path follows the standard path for this
cell. The AFLOW standard by Setyawan and Curtarolo is used
(https://doi.org/10.1016/j.commatsci.2010.05.010).
"""
)
class ElectronicDOS(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
a_elastic=dict(type=InnerDoc),
description="""
Stores the electronic density of states (DOS).
"""
)
scc_index = Quantity(
type=int,
description="""
Index of the single configuration calculation that contains the density
of states.
"""
)
fermi_level = Quantity(
type=np.dtype(np.float64),
unit=units.J,
description="""
Fermi level reported for the density of states.
"""
)
energies = Quantity(
type=np.dtype(np.float64),
shape=["1..*"],
unit=units.J,
description="""
Array containing the set of discrete energy values with respect to the
top of the valence band for the density of states (DOS).
"""
)
values = Quantity(
type=np.dtype(np.float64),
shape=["1..2", "1..*"],
unit=units.J**(-1),
description="""
Values (number of states for a given energy, the set of discrete energy
values is given in dos_energies) of density of states. The values are
given as states/energy.
"""
)
class Properties(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
......@@ -612,14 +420,14 @@ class Properties(MSection):
)
atomic_density = Quantity(
type=float,
unit=units.m**(-3),
unit="1 / m ** 3",
description="""
Atomic density of the material (atoms/volume)."
"""
)
mass_density = Quantity(
type=float,
unit=units.kg / units.m**3,
unit="kg / m ** 3",
description="""
Mass density of the material.
"""
......@@ -630,8 +438,20 @@ class Properties(MSection):
Code dependent energy values, corrected to be per formula unit.
"""
)
electronic_band_structure = SubSection(sub_section=ElectronicBandStructure.m_def, repeats=False)
electronic_dos = SubSection(sub_section=ElectronicDOS.m_def, repeats=False)
electronic_band_structure = Quantity(
type=Reference(section_k_band.m_def),
shape=[],
description="""
Reference to an electronic band structure.
"""
)
electronic_dos = Quantity(
type=Reference(section_dos.m_def),
shape=[],
description="""
Reference to an electronic density of states.
"""
)
class section_encyclopedia(MSection):
......
......@@ -119,3 +119,204 @@ try:
convert_unit_function("m", "angstrom")(self.cell))
except Exception as e:
self.logger.warn("failed to get special points/xtal structure", exc_info=e)
# Loop over bands
for band_data in bands:
# Add reciprocal cell and brillouin zone
self.get_reciprocal_cell(band_data, prim_atoms, orig_atoms)
self.get_brillouin_zone(band_data)
# If the parser has reported a valence band maximum, we can add
# band gap information.
if valence_band_maximum is not None:
kpoints = []
energies = []
segments = band_data.section_k_band_segment
if not segments:
return
# Loop over segments
for segment_src in segments:
try:
seg_k_points = segment_src.band_k_points
seg_energies = segment_src.band_energies
except Exception:
return
if seg_k_points is None or seg_energies is None:
return
else:
seg_energies = seg_energies.magnitude
seg_energies = np.swapaxes(seg_energies, 1, 2)
kpoints.append(seg_k_points)
energies.append(seg_energies)
# Continue to calculate band gaps and other properties.
kpoints = np.concatenate(kpoints, axis=0)
energies = np.concatenate(energies, axis=2)
# If the parser has reported a valence band maximum, we can add
# band gap information.
self.get_band_gaps(band_data, energies, kpoints, valence_band_maximum)
def get_band_gaps(self, band_structure: ElectronicBandStructure, energies: np.array, path: np.array) -> None:
"""Given the band structure and fermi level, calculates the band gap
for spin channels and also reports the total band gap as the minum gap
found.
"""
# Handle spin channels separately to find gaps for spin up and down
reciprocal_cell = band_structure.reciprocal_cell.magnitude
fermi_level = band_structure.fermi_level
n_channels = energies.shape[0]
gaps: List[BandGap] = [None, None]
for channel in range(n_channels):
channel_energies = energies[channel, :, :]
lower_defined = False
upper_defined = False
num_bands = channel_energies.shape[0]
band_indices = np.arange(num_bands)
band_minima_idx = channel_energies.argmin(axis=1)
band_maxima_idx = channel_energies.argmax(axis=1)
band_minima = channel_energies[band_indices, band_minima_idx]
band_maxima = channel_energies[band_indices, band_maxima_idx]
# Add a tolerance to minima and maxima
band_minima_tol = band_minima + config.normalize.fermi_level_precision
band_maxima_tol = band_maxima - config.normalize.fermi_level_precision
for band_idx in range(num_bands):
band_min = band_minima[band_idx]
band_max = band_maxima[band_idx]
band_min_tol = band_minima_tol[band_idx]
band_max_tol = band_maxima_tol[band_idx]
# If any of the bands band crosses the Fermi level, there is no
# band gap
if band_min_tol <= fermi_level and band_max_tol >= fermi_level:
break
# Whole band below Fermi level, save the current highest
# occupied band point
elif band_min_tol <= fermi_level and band_max_tol <= fermi_level:
gap_lower_energy = band_max
gap_lower_idx = band_maxima_idx[band_idx]
lower_defined = True
# Whole band above Fermi level, save the current lowest
# unoccupied band point
elif band_min_tol >= fermi_level:
gap_upper_energy = band_min
gap_upper_idx = band_minima_idx[band_idx]
upper_defined = True
break
# If a highest point of the valence band and a lowest point of the
# conduction band are found, and the difference between them is
# positive, save the information location and value.
if lower_defined and upper_defined and gap_upper_energy - gap_lower_energy >= 0:
# See if the gap is direct or indirect by comparing the k-point
# locations with some tolerance
k_point_lower = path[gap_lower_idx]
k_point_upper = path[gap_upper_idx]
k_point_distance = self.get_k_space_distance(reciprocal_cell, k_point_lower, k_point_upper)
is_direct_gap = k_point_distance <= config.normalize.k_space_precision
gap = BandGap()
gap.type = "direct" if is_direct_gap else "indirect"
gap.value = float(gap_upper_energy - gap_lower_energy)
gap.conduction_band_min_k_point = k_point_upper
gap.conduction_band_min_energy = float(gap_upper_energy)
gap.valence_band_max_k_point = k_point_lower
gap.valence_band_max_energy = float(gap_lower_energy)
gaps[channel] = gap