Source code for oncvpsp_tools.input

"""Classes for handling ONCVPSP input files."""

import subprocess
from collections import UserList
from dataclasses import dataclass
from typing import Generic, Optional, TypeVar

from .utils import sanitize


class ONCVPSPEntry:
    """Generic class for an entry in an ONCVPSP input file."""

    @property
    def columns(self) -> str:
        """Return the column headers for the entry."""
        return "# " + " ".join([f"{str(k): >8}" for k in self.__dict__.keys()])[2:]

    @property
    def content(self) -> str:
        """Return the content of the entry."""
        return " ".join([f"{str(v): >8}" for v in self.__dict__.values() if v is not None])

    def to_str(self) -> str:
        """Return the text representation of the entry."""
        return f"{self.columns}\n{self.content}"

    def __repr__(self):
        """Make the repr of the entry very simple."""
        return str(self.__dict__)


T = TypeVar("T")


class ONCVPSPList(UserList, Generic[T]):
    """Generic class for an entry in an ONCVPSP input file that contains multiple elements and emulates a list."""

    def __init__(self, data):
        """Create an :class:`ONCVPSPList` object."""
        super().__init__(data)

    @property
    def columns(self) -> str:
        """Return the column headers for the list."""
        if self.data and isinstance(self.data[0], ONCVPSPEntry):
            return self.data[0].columns
        else:
            return ""

    def to_str(self, print_length=False) -> str:
        """Return the text representation of the list."""
        out = []
        if print_length:
            out.append(f"{len(self): >8}")
        out.append(self.columns)
        out += [
            d.to_str(print_length) if isinstance(d, ONCVPSPList) else d.content for d in self.data
        ]
        return "\n".join(out)


@dataclass(repr=False)
class ONCVPSPAtom(ONCVPSPEntry):
    """Class for the atom block in an ONCVPSP input file."""

    atsym: str
    z: float
    nc: int
    nv: int
    iexc: int
    psfile: str


@dataclass(repr=False)
class ONCVPSPConfigurationSubshell(ONCVPSPEntry):
    """Class for a subshell in an ONCVPSP configuration block."""

    n: int
    l: int
    f: float
    energy: float = -0.0


@dataclass(repr=False)
class ONCVPSPOptimizationChannel(ONCVPSPEntry):
    """Class for an optimization channel in an ONCVPSP input file."""

    l: int
    rc: float
    ep: float
    ncon: int
    nbas: int
    qcut: float


@dataclass(repr=False)
class ONCVPSPLocalPotential(ONCVPSPEntry):
    """Class for the local potential block in an ONCVPSP input file."""

    lloc: int
    lpopt: int
    rc: float
    dvloc0: float


@dataclass(repr=False)
class ONCVPSPVKBProjector(ONCVPSPEntry):
    """Class for a VKB projector in an ONCVPSP input file."""

    l: int
    nproj: int
    debl: float


@dataclass(repr=False)
class ONCVPSPModelCoreCharge(ONCVPSPEntry):
    """Class for the model core charge block in an ONCVPSP input file."""

    icmod: int
    fcfact: float
    rcfact: Optional[float] = None


@dataclass(repr=False)
class ONCVPSPLogDerivativeAnalysis(ONCVPSPEntry):
    """Class for the log derivative analysis block in an ONCVPSP input file."""

    epsh1: float
    epsh2: float
    depsh: float


@dataclass(repr=False)
class ONCVPSPOutputGrid(ONCVPSPEntry):
    """Class for the output grid block in an ONCVPSP input file."""

    rlmax: float
    drl: float


[docs]@dataclass class ONCVPSPInput: """Class for the contents of an ONCVPSP input file. The :class:`ONCVPSPInput` class is a dataclass that helps a user interact with input files for ``oncvpsp.x``. Typically, a user will create an :class:`ONCVPSPInput` object from an input file using the :meth:`from_file` method, as follows :: from oncvpsp_tools import ONCVPSPInput inp = ONCVPSPInput.from_file("path/to/input.in") :class:`ONCVPSPInput` objects -- being a :class:`dataclass` -- have the same attributes as the input parameters (listed below). Use these to interact with the contents of the input file. :param atom: The atom block of the input file :type atom: :class:`ONCVPSPAtom` :param reference_configuration: The reference configuration block of the input file :type reference_configuration: :class:`ONCVPSPList[ONCVPSPConfigurationSubshell]` :param lmax: The lmax block of the input file :type lmax: int :param optimization: The optimization block of the input file :type optimization: :class:`ONCVPSPList[ONCVPSPOptimizationChannel]` :param local_potential: The local potential block of the input file :type local_potential: :class:`ONCVPSPLocalPotential` :param vkb_projectors: The VKB projectors block of the input file :type vkb_projectors: :class:`ONCVPSPList[ONCVPSPVKBProjector]` :param model_core_charge: The model core charge block of the input file :type model_core_charge: :class:`ONCVPSPModelCoreCharge` :param log_derivative_analysis: The log derivative analysis block of the input file :type log_derivative_analysis: :class:`ONCVPSPLogDerivativeAnalysis` :param output_grid: The output grid block of the input file :type output_grid: :class:`ONCVPSPOutputGrid` :param test_configurations: The test configurations block of the input file :type test_configurations: :class:`ONCVPSPList[ONCVPSPList[ONCVPSPConfigurationSubshell]]` """ atom: ONCVPSPAtom reference_configuration: ONCVPSPList[ONCVPSPConfigurationSubshell] lmax: int optimization: ONCVPSPList[ONCVPSPOptimizationChannel] local_potential: ONCVPSPLocalPotential vkb_projectors: ONCVPSPList[ONCVPSPVKBProjector] model_core_charge: ONCVPSPModelCoreCharge log_derivative_analysis: ONCVPSPLogDerivativeAnalysis output_grid: ONCVPSPOutputGrid test_configurations: ONCVPSPList[ONCVPSPList[ONCVPSPConfigurationSubshell]]
[docs] @classmethod def from_file(cls, filename: str): """Create an :class:`ONCVPSPInput` object from an ONCVPSP input file.""" with open(filename, "r") as f: txt = f.read() return cls.from_str(txt)
[docs] @classmethod def from_str(cls, txt: str): """Create an :class:`ONCVPSPInput` object from a string.""" lines = [line.strip() for line in txt.split("\n")] content = [line for line in lines if not line.startswith("#") and line] # atom atom = ONCVPSPAtom(*[sanitize(v) for v in content[0].split()]) # reference configuration ntot = atom.nc + atom.nv reference_configuration: ONCVPSPList[ONCVPSPConfigurationSubshell] = ONCVPSPList( [ ONCVPSPConfigurationSubshell(*[sanitize(v) for v in line.split()]) for line in content[1 : ntot + 1] ] ) # lmax lmax = int(content[ntot + 1]) # optimization istart = ntot + 2 iend = istart + lmax + 1 optimization: ONCVPSPList[ONCVPSPOptimizationChannel] = ONCVPSPList( [ ONCVPSPOptimizationChannel(*[sanitize(v) for v in line.split()]) for line in content[istart:iend] ] ) # local potential local_potential = ONCVPSPLocalPotential(*[sanitize(v) for v in content[iend].split()]) # VKB projectors istart = iend + 1 iend = istart + lmax + 1 vkb: ONCVPSPList[ONCVPSPVKBProjector] = ONCVPSPList( [ ONCVPSPVKBProjector(*[sanitize(v) for v in line.split()]) for line in content[istart:iend] ] ) # model core charge mcc = ONCVPSPModelCoreCharge(*[sanitize(v) for v in content[iend].split()]) iend += 1 # log derivative analysis log_derivative_analysis = ONCVPSPLogDerivativeAnalysis( *[sanitize(v) for v in content[iend].split()] ) iend += 1 # output grid output_grid = ONCVPSPOutputGrid(*[sanitize(v) for v in content[iend].split()]) iend += 1 # test configurations ncvf = int(content[iend]) iend += 1 test_configs: ONCVPSPList[ONCVPSPList[ONCVPSPConfigurationSubshell]] = ONCVPSPList([]) for _ in range(ncvf): nv = int(content[iend]) istart = iend + 1 iend = istart + nv test_configs.append( ONCVPSPList( [ ONCVPSPConfigurationSubshell(*[sanitize(v) for v in line.split()]) for line in content[istart:iend] ], ) ) return cls( atom, reference_configuration, lmax, optimization, local_potential, vkb, mcc, log_derivative_analysis, output_grid, test_configs, )
[docs] def to_str(self) -> str: """Return the text representation of the ONCVPSP input file.""" return "\n".join( [ "# ATOM AND REFERENCE CONFIGURATION", self.atom.to_str(), self.reference_configuration.to_str(), "# PSEUDOPOTENTIAL AND OPTIMIZATION", "# lmax", f"{self.lmax: >8}", self.optimization.to_str(), "# LOCAL POTENTIAL", self.local_potential.to_str(), "# VANDERBILT-KLEINMAN-BYLANDER PROJECTORS", self.vkb_projectors.to_str(), "# MODEL CORE CHARGE", self.model_core_charge.to_str(), "# LOG DERIVATIVE ANALYSIS", self.log_derivative_analysis.to_str(), "# OUTPUT GRID", self.output_grid.to_str(), "# TEST CONFIGURATIONS", "# ncnf", self.test_configurations.to_str(print_length=True), ] ).replace("\n\n", "\n")
[docs] def to_file(self, filename: str): """Write the ONCVPSP input file to disk.""" with open(filename, "w") as f: f.write(self.to_str())
[docs] def run(self, oncvpsp_command="oncvpso.x"): """Run the ONCVPSP executable and return the output.""" from oncvpsp_tools.output import ONCVPSPOutput # Write the input file self.to_file("tmp.oncvpsp.in") # Run oncvpsp.x with open("tmp.oncvpsp.in", "r") as input_file: result = subprocess.run( oncvpsp_command, stdin=input_file, capture_output=True, shell=True, text=True ) # Parse and return the result try: return ONCVPSPOutput.from_str(result.stdout) except Exception: output_file = "tmp.oncvpsp.out" with open(output_file, "w") as f: f.write(result.stdout) raise ValueError(f"ONCVPSP failed; inspect the output ({output_file})")