Source code for tests.test_diffpes.test_inout.test_doscar

"""Validate VASP DOSCAR parsing.

Covers legacy and full density-of-states carriers, spin channels, projected blocks, and malformed or truncated input.
"""

import io
import tempfile
from pathlib import Path

import chex
import jax.numpy as jnp
import pytest
from beartype.typing import TextIO

import diffpes
from diffpes.inout import (
    read_chgcar,
    read_doscar,
    read_eigenval,
    read_kpoints,
    read_poscar,
    read_procar,
)
from diffpes.types import (
    BandStructure,
    FullDensityOfStates,
    SOCVolumetricData,
    SpinBandStructure,
    SpinOrbitalProjection,
    VolumetricData,
    make_orbital_projection,
    make_spin_orbital_projection,
)

_FIXTURES_DIR: Path = Path(__file__).resolve().parent / "fixtures"


[docs] class TestReadDoscar(chex.TestCase): """Validate :func:`diffpes.inout.read_doscar`. Verifies that the DOSCAR parser produces a valid DensityOfStates PyTree with correct array shapes and expected numeric values from the minimal fixture. :see: :func:`~diffpes.inout.read_doscar` """
[docs] def test_parses_minimal_doscar(self) -> None: """Read minimal DOSCAR fixture and assert shape and key values of DensityOfStates. The test loads the ``fixtures/DOSCAR`` file. It checks the array shapes and the scalar Fermi energy. It compares selected elements with the known fixture values. These checks verify the DOSCAR header and data block. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" path: Path dos: diffpes.types.DensityOfStates | diffpes.types.FullDensityOfStates path = _FIXTURES_DIR / "DOSCAR" dos = read_doscar(str(path)) chex.assert_shape(dos.energy, (5,)) chex.assert_shape(dos.total_dos, (5,)) chex.assert_shape(dos.fermi_energy, ()) chex.assert_trees_all_close( dos.fermi_energy, jnp.float64(0.5), atol=1e-12 ) chex.assert_trees_all_close( dos.energy[0], jnp.float64(-2.0), atol=1e-12 ) chex.assert_trees_all_close( dos.total_dos[2], jnp.float64(0.5), atol=1e-12 )
[docs] class TestReadDoscarFull(chex.TestCase): """Validate :func:`diffpes.inout.read_doscar` with ``return_mode='full'``. Validates the full-mode DOSCAR parser that returns ``FullDensityOfStates`` objects containing spin-resolved total DOS, integrated DOS, and optionally per-atom projected DOS (PDOS). Covers spin-polarized files (both channels), non-spin files (spin-down fields are None), PDOS-containing files, and the legacy fallback for spin-polarized data. :see: :func:`~diffpes.inout.read_doscar` """
[docs] def test_spin_doscar_full(self) -> None: """Read spin-polarized DOSCAR in full mode and verify both spin channels. Parses the DOSCAR_spin fixture with ``return_mode="full"``. The test checks the result type and the shape of each DOS array. It checks that both spin-down arrays exist. It compares the Fermi energy and the first value of each spin channel with fixture values. The comparisons use ``atol=1e-12``. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" path: Path dos: diffpes.types.DensityOfStates | diffpes.types.FullDensityOfStates path = _FIXTURES_DIR / "DOSCAR_spin" dos = read_doscar(str(path), return_mode="full") assert isinstance(dos, FullDensityOfStates) chex.assert_shape(dos.energy, (5,)) chex.assert_shape(dos.total_dos_up, (5,)) assert dos.total_dos_down is not None chex.assert_shape(dos.total_dos_down, (5,)) chex.assert_shape(dos.integrated_dos_up, (5,)) assert dos.integrated_dos_down is not None chex.assert_shape(dos.integrated_dos_down, (5,)) chex.assert_trees_all_close( dos.fermi_energy, jnp.float64(0.5), atol=1e-12 ) chex.assert_trees_all_close( dos.total_dos_up[0], jnp.float64(0.10), atol=1e-12 ) chex.assert_trees_all_close( dos.total_dos_down[0], jnp.float64(0.08), atol=1e-12 )
[docs] def test_nonspin_doscar_full(self) -> None: """Read non-spin-polarized DOSCAR in full mode and verify spin-down fields are None. Parses the standard DOSCAR fixture (non-spin-polarized) with ``return_mode="full"``. The test checks the result type and the three spin-up array shapes. It verifies that both spin-down arrays are ``None``. These checks confirm detection of non-spin data. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" path: Path dos: diffpes.types.DensityOfStates | diffpes.types.FullDensityOfStates path = _FIXTURES_DIR / "DOSCAR" dos = read_doscar(str(path), return_mode="full") assert isinstance(dos, FullDensityOfStates) chex.assert_shape(dos.energy, (5,)) chex.assert_shape(dos.total_dos_up, (5,)) assert dos.total_dos_down is None chex.assert_shape(dos.integrated_dos_up, (5,)) assert dos.integrated_dos_down is None
[docs] def test_pdos_doscar_full(self) -> None: """Read DOSCAR containing per-atom PDOS blocks in full mode. Parses the DOSCAR_pdos fixture with ``return_mode="full"``. The test checks the result type and verifies that ``pdos`` exists. The PDOS shape represents two atoms, three energies, and ten columns. The test also verifies ``natoms=2``. These checks validate the PDOS block after the total DOS header. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" path: Path dos: diffpes.types.DensityOfStates | diffpes.types.FullDensityOfStates path = _FIXTURES_DIR / "DOSCAR_pdos" dos = read_doscar(str(path), return_mode="full") assert isinstance(dos, FullDensityOfStates) assert dos.pdos is not None chex.assert_shape(dos.pdos, (2, 3, 10)) assert dos.natoms == 2
[docs] def test_spin_doscar_legacy(self) -> None: """Read a legacy spin-polarized DOSCAR and verify the spin-up result. Parses the DOSCAR_spin fixture with ``return_mode="legacy"``. The test verifies a plain ``DensityOfStates`` result and both array shapes. It compares the first total DOS value with the spin-up value. The comparison uses ``atol=1e-12``. This test covers the compatible path that discards spin-down data. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" path: Path dos: diffpes.types.DensityOfStates | diffpes.types.FullDensityOfStates path = _FIXTURES_DIR / "DOSCAR_spin" dos = read_doscar(str(path), return_mode="legacy") chex.assert_shape(dos.energy, (5,)) chex.assert_shape(dos.total_dos, (5,)) chex.assert_trees_all_close( dos.total_dos[0], jnp.float64(0.10), atol=1e-12 )
[docs] class TestReadDoscarPdosHeader(chex.TestCase): """Validate :func:`read_doscar` PDOS parsing with per-atom header lines. :see: :func:`~diffpes.inout.read_doscar` """
[docs] def test_pdos_with_header_line(self) -> None: """Exercise DOSCAR parsing with a header for each atom. The test uses the DOSCAR_pdos_header fixture where each atom's PDOS block starts with a 4-value header line (EMIN EMAX NEDOS EFERMI). The four values satisfy the column-count condition. Therefore, the parser executes the applicable header path. The test checks the PDOS shape for one atom, three energies, and nine orbital columns. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" path: Path dos: diffpes.types.DensityOfStates | diffpes.types.FullDensityOfStates path = _FIXTURES_DIR / "DOSCAR_pdos_header" dos = read_doscar(str(path), return_mode="full") assert isinstance(dos, FullDensityOfStates) assert dos.pdos is not None chex.assert_shape(dos.pdos, (1, 3, 9))
[docs] def test_pdos_early_break_on_empty_line(self) -> None: """Exercise PDOS parsing with fewer rows than ``NEDOS``. The test writes a DOSCAR where natoms=1, NEDOS=3 but the PDOS block has only 1 data row followed by an empty line. This causes the ``for j in range(1, nedos):`` loop to hit an empty ``row_line`` and execute the ``break`` on line 230. Asserts the result is a ``FullDensityOfStates`` (parse continues with truncated data). Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" fh: TextIO content: str tmpname: str dos: diffpes.types.DensityOfStates | diffpes.types.FullDensityOfStates content = ( " 1\n" " unknown system\n" " 1.0\n" " 0.0 0.0 0.0\n" " 0.0 0.0 0.0\n" " -2.0 1.0 3 0.5\n" " -2.0 0.1 0.0\n" " -1.0 0.2 0.1\n" " 1.0 0.3 0.4\n" " -2.0 0.05 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01\n" "\n" ) with tempfile.NamedTemporaryFile( mode="w", suffix=".DOSCAR", delete=False ) as fh: fh.write(content) tmpname = fh.name try: dos = read_doscar(tmpname, return_mode="full") assert isinstance(dos, FullDensityOfStates) finally: import os os.unlink(tmpname)
[docs] def test_pdos_header_then_empty_line_breaks(self) -> None: """Exercise PDOS parsing with an empty line after the header. The test writes a DOSCAR with ``natoms=1`` and ``NEDOS=3``. Its PDOS block has a four-value header followed by an empty line. The parser reads the header (line 204), then reads the empty line as ``pdos_ncols_check`` and hits ``if not pdos_ncols_check.strip(): break`` on line 205-206. The test asserts the result is a ``FullDensityOfStates`` with ``pdos=None`` (no PDOS data collected before the break). Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" fh: TextIO content: str tmpname: str dos: diffpes.types.DensityOfStates | diffpes.types.FullDensityOfStates content = ( " 1\n" " unknown system\n" " 1.0\n" " 0.0 0.0 0.0\n" " 0.0 0.0 0.0\n" " -2.0 1.0 3 0.5\n" " -2.0 0.1 0.0\n" " -1.0 0.2 0.1\n" " 1.0 0.3 0.4\n" " -2.0 1.0 3 0.5\n" "\n" ) with tempfile.NamedTemporaryFile( mode="w", suffix=".DOSCAR", delete=False ) as fh: fh.write(content) tmpname = fh.name try: dos = read_doscar(tmpname, return_mode="full") assert isinstance(dos, FullDensityOfStates) assert dos.pdos is None finally: import os os.unlink(tmpname)
[docs] def test_pdos_header_inner_loop_empty_row_breaks(self) -> None: """Exercise the PDOS header loop with an empty data row. The test writes a DOSCAR with ``natoms=1`` and ``NEDOS=3``. The PDOS block has a header, one valid row, and one empty row. The inner loop reads the empty ``row_line`` and hits ``if not row_line.strip(): break`` on line 217-218. Asserts the result is a ``FullDensityOfStates``. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" fh: TextIO content: str tmpname: str dos: diffpes.types.DensityOfStates | diffpes.types.FullDensityOfStates content = ( " 1\n" " unknown system\n" " 1.0\n" " 0.0 0.0 0.0\n" " 0.0 0.0 0.0\n" " -2.0 1.0 3 0.5\n" " -2.0 0.1 0.0\n" " -1.0 0.2 0.1\n" " 1.0 0.3 0.4\n" " -2.0 1.0 3 0.5\n" " -2.0 0.05 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01\n" "\n" ) with tempfile.NamedTemporaryFile( mode="w", suffix=".DOSCAR", delete=False ) as fh: fh.write(content) tmpname = fh.name try: dos = read_doscar(tmpname, return_mode="full") assert isinstance(dos, FullDensityOfStates) finally: import os os.unlink(tmpname)