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

Added proper regression tests for identifying the band structure information.

parent f8a5a29d
Pipeline #69722 passed with stages
in 25 minutes and 25 seconds
......@@ -278,7 +278,7 @@ class Method(MSection):
"""
)
method_type = Quantity(
type=MEnum("DFT", "GW", "unavailable"),
type=MEnum("DFT", "DFT+U", "GW", "unavailable"),
description="""
Generic name for the used methodology.
"""
......
......@@ -1489,9 +1489,8 @@ class PropertiesNormalizer():
fermi_level = band_structure.fermi_level
n_channels = energies.shape[0]
gaps = []
gaps: List[BandGap] = [None, None]
for channel in range(n_channels):
gap = BandGap()
channel_energies = energies[channel, :, :]
lower_defined = False
upper_defined = False
......@@ -1541,27 +1540,38 @@ class PropertiesNormalizer():
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
gaps.append(gap)
# For unpolarized calculations we simply report the gap if it is found.
if n_channels == 1:
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap, gaps[0])
elif n_channels == 2:
# The band gap for the structure is the one with minimum gap
if gaps[0].value <= gaps[1].value:
if gaps[0] is not None:
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap, gaps[0])
# For polarized calculations we report the gap separately for both
# channels. Also we report the smaller gap as the the total gap for the
# calculation.
elif n_channels == 2:
if gaps[0] is not None:
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap_spin_up, gaps[0])
if gaps[1] is not None:
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap_spin_down, gaps[1])
if gaps[0] is not None and gaps[1] is not None:
if gaps[0].value <= gaps[1].value:
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap, gaps[0])
else:
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap, gaps[1])
else:
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap, gaps[1])
# The band gap is reported individually for both spin channels
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap_spin_up, gaps[0])
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap_spin_down, gaps[1])
if gaps[0] is not None:
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap, gaps[0])
elif gaps[1] is not None:
band_structure.m_add_sub_section(ElectronicBandStructure.band_gap, gaps[1])
def get_k_space_distance(self, reciprocal_cell: np.array, point1: np.array, point2: np.array) -> float:
"""Used to calculate the Euclidian distance of two points in k-space,
......@@ -1650,6 +1660,7 @@ class PropertiesNormalizer():
# The path and energies are merged into single arrays
path = np.concatenate(path, axis=0)
energies = np.swapaxes(np.concatenate(energies, axis=1), 1, 2)
band_structure.path = path
band_structure.energies = energies
......
......@@ -158,9 +158,36 @@ def one_d() -> LocalBackend:
@pytest.fixture(scope='session')
def bands_insulator_indirect() -> LocalBackend:
def bands_unpolarized_gap_indirect() -> LocalBackend:
parser_name = "parsers/vasp"
filepath = "tests/data/normalizers/band_structure/insulator/vasprun.xml.bands.xz"
filepath = "tests/data/normalizers/band_structure/unpolarized_gap/vasprun.xml.bands"
backend = parse_file((parser_name, filepath))
backend = run_normalize(backend)
return backend
@pytest.fixture(scope='session')
def bands_polarized_no_gap() -> LocalBackend:
parser_name = "parsers/vasp"
filepath = "tests/data/normalizers/band_structure/polarized_no_gap/vasprun.xml.bands"
backend = parse_file((parser_name, filepath))
backend = run_normalize(backend)
return backend
@pytest.fixture(scope='session')
def bands_unpolarized_no_gap() -> LocalBackend:
parser_name = "parsers/vasp"
filepath = "tests/data/normalizers/band_structure/unpolarized_no_gap/vasprun.xml.bands"
backend = parse_file((parser_name, filepath))
backend = run_normalize(backend)
return backend
@pytest.fixture(scope='session')
def bands_polarized_gap_indirect() -> LocalBackend:
parser_name = "parsers/vasp"
filepath = "tests/data/normalizers/band_structure/polarized_gap/vasprun.xml.bands"
backend = parse_file((parser_name, filepath))
backend = run_normalize(backend)
return backend
......@@ -23,7 +23,7 @@ from nomad.utils import hash
from nomad.parsing import LocalBackend
from nomad.normalizing import structure
from nomad.metainfo.encyclopedia import Encyclopedia
from tests.normalizing.conftest import run_normalize_for_structure, geometry_optimization, molecular_dynamics, phonon, two_d, bulk, bands_insulator_indirect # pylint: disable=unused-import
from tests.normalizing.conftest import run_normalize_for_structure, geometry_optimization, molecular_dynamics, phonon, two_d, bulk, bands_unpolarized_no_gap, bands_polarized_no_gap, bands_unpolarized_gap_indirect, bands_polarized_gap_indirect # pylint: disable=unused-import
ureg = UnitRegistry()
......@@ -451,14 +451,66 @@ def test_method_gw_metainfo(gw):
assert enc.method.gw_starting_point == "GGA_C_PBE+0.75*GGA_X_PBE+0.25*HF_X"
def test_band_structure(bands_insulator_indirect):
# Single channel, insulator, indirect
enc = bands_insulator_indirect.get_mi2_section(Encyclopedia.m_def)
def test_band_structure(bands_unpolarized_no_gap, bands_polarized_no_gap, bands_unpolarized_gap_indirect, bands_polarized_gap_indirect):
# Unpolarized, no gaps
enc = bands_unpolarized_no_gap.get_mi2_section(Encyclopedia.m_def)
properties = enc.properties
bs = properties.electronic_band_structure
energies = bs.energies
path = bs.path
assert len(energies.shape) == 3
assert energies.shape[0] == 1
assert energies.shape[2] == path.shape[0]
assert bs.band_gap is None
assert bs.band_gap_spin_up is None
assert bs.band_gap_spin_down is None
# Polarized, no gaps
enc = bands_polarized_no_gap.get_mi2_section(Encyclopedia.m_def)
properties = enc.properties
bs = properties.electronic_band_structure
energies = bs.energies
path = bs.path
assert len(energies.shape) == 3
assert energies.shape[2] == path.shape[0]
assert energies.shape[0] == 2
assert bs.band_gap is None
assert bs.band_gap_spin_up is None
assert bs.band_gap_spin_down is None
# Unpolarized, finite gap, indirect
enc = bands_unpolarized_gap_indirect.get_mi2_section(Encyclopedia.m_def)
properties = enc.properties
bs = properties.electronic_band_structure
assert bs is not None
gap_ev = (bs.band_gap.value * ureg.J).to(ureg.eV).magnitude
assert gap_ev == pytest.approx(0.99, 0.01)
assert gap_ev == pytest.approx(0.62, 0.01)
assert bs.band_gap.type == "indirect"
energies = bs.energies
assert energies.shape == (1, 68, 190)
path = bs.path
assert len(energies.shape) == 3
assert energies.shape[0] == 1
assert energies.shape[2] == path.shape[0]
assert bs.band_gap_spin_up is None
assert bs.band_gap_spin_down is None
# Polarized, finite gap, indirect
enc = bands_polarized_gap_indirect.get_mi2_section(Encyclopedia.m_def)
properties = enc.properties
bs = properties.electronic_band_structure
gap = bs.band_gap
gap_up = bs.band_gap_spin_up
gap_down = bs.band_gap_spin_down
gap_ev = (gap.value * ureg.J).to(ureg.eV).magnitude
gap_up_ev = (gap_up.value * ureg.J).to(ureg.eV).magnitude
gap_down_ev = (gap_down.value * ureg.J).to(ureg.eV).magnitude
assert gap_up.type == "indirect"
assert gap_down.type == "indirect"
assert gap_up_ev != gap_down_ev
assert gap_up_ev == gap_ev
assert gap_up_ev == pytest.approx(0.956, 0.01)
assert gap_down_ev == pytest.approx(1.230, 0.01)
energies = bs.energies
path = bs.path
assert len(energies.shape) == 3
assert energies.shape[0] == 2
assert energies.shape[2] == path.shape[0]
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment