Source code for mammos_dft.db

"""Functions for reading tables."""

import pathlib
import shutil
from textwrap import dedent

import mammos_entity as me
import mammos_units as u
import pandas as pd
from pydantic import ConfigDict
from pydantic.dataclasses import dataclass
from rich import print

DATA_DIR = pathlib.Path(__file__).parent / "data"


def _check_short_label(short_label: str) -> tuple[str, int]:
    """Check that short label follows the standards and returns material parameters.

    Args:
        short_label: Short label containing chemical formula and space group
            number separated by a hyphen.

    Returns:
        Chemical formula and space group number.

    Raises:
        ValueError: Wrong format.

    """
    short_label_list = short_label.split("-")
    if len(short_label_list) != 2:
        raise ValueError(
            dedent(
                """
                Wrong format for `short_label`.
                Please use the format <chemical_formula>-<space_group_number>.
                """
            )
        )
    chemical_formula = short_label_list[0]
    space_group_number = int(short_label_list[1])
    return chemical_formula, space_group_number


[docs] @dataclass(frozen=True, config=ConfigDict(arbitrary_types_allowed=True)) class MicromagneticProperties: """Result object containing micromagnetic properties.""" Ms_0: me.Entity """Saturation magnetisation at T=0K.""" K1_0: me.Entity """Uniaxial anisotropy constant K1 at T=0K."""
[docs] def get_micromagnetic_properties( chemical_formula: str | None = None, space_group_name: str | None = None, space_group_number: int | None = None, cell_length_a: float | None = None, cell_length_b: float | None = None, cell_length_c: float | None = None, cell_angle_alpha: float | None = None, cell_angle_beta: float | None = None, cell_angle_gamma: float | None = None, cell_volume: float | None = None, ICSD_label: str | None = None, OQMD_label: str | None = None, print_info: bool = False, ) -> MicromagneticProperties: """Get micromagnetic intrinsic properties at 0K temperature from table. Given certain material information, this function searches and retrieves the following values from a local database: * `Ms_0`: spontaneous magnetisation at temperature 0K expressed in A/m. * `K_0`: magnetocrystalline anisotropy at temperature 0K expressed in J/m^3. Args: chemical_formula: Chemical formula. space_group_name: Space group name. space_group_number: Space group number. cell_length_a: Cell length a. cell_length_b: Cell length b. cell_length_c: Cell length c. cell_angle_alpha: Cell angle alpha. cell_angle_beta: Cell angle beta. cell_angle_gamma: Cell angle gamma. cell_volume: Cell volume. ICSD_label: Label in the NIST Inorganic Crystal Structure Database. OQMD_label: Label in the the Open Quantum Materials Database. print_info: Print info Returns: Saturation magnetisation and uniaxial anisotropy at T=0. Raises: ValueError: Wrong format for `short_label`. Examples: >>> import mammos_dft.db >>> mammos_dft.db.get_micromagnetic_properties("Nd2Fe14B") MicromagneticProperties(Ms_0=..., K1_0=...) """ # TODO: implement CIF parsing material = _find_unique_material( print_info=print_info, chemical_formula=chemical_formula, space_group_name=space_group_name, space_group_number=space_group_number, cell_length_a=cell_length_a, cell_length_b=cell_length_b, cell_length_c=cell_length_c, cell_angle_alpha=cell_angle_alpha, cell_angle_beta=cell_angle_beta, cell_angle_gamma=cell_angle_gamma, cell_volume=cell_volume, ICSD_label=ICSD_label, OQMD_label=OQMD_label, ) return MicromagneticProperties( me.Ms(material.SpontaneousMagnetization), me.Ku(material.UniaxialAnisotropyConstant), )
[docs] def find_materials(**kwargs) -> pd.DataFrame: """Find materials in database. This function retrieves one or known materials from the database `db.csv` by filtering for any requirements given in `kwargs`. Args: kwargs: Selection criteria. Returns: Dataframe containing materials with requested qualities. Possibly empty. """ df = pd.read_csv( DATA_DIR / "db.csv", converters={ "chemical_formula": str, "space_group_name": str, "space_group_number": int, "cell_length_a": u.Quantity, "cell_length_b": u.Quantity, "cell_length_c": u.Quantity, "cell_angle_alpha": u.Quantity, "cell_angle_beta": u.Quantity, "cell_angle_gamma": u.Quantity, "cell_volume": u.Quantity, "ICSD_label": str, "OQMD_label": str, "label": str, "SpontaneousMagnetization": u.Quantity, "UniaxialAnisotropyConstant": u.Quantity, }, ) for key, value in kwargs.items(): if value is not None: if isinstance(value, u.Quantity): df = df[df[key] == value.to(df[key].unit)] else: df = df[df[key] == value] return df
def _find_unique_material(print_info: bool = False, **kwargs) -> pd.DataFrame: """Find unique material in database. This function retrieves one material from the database `db.csv` by filtering for any requirements given in **kwargs. If no or more than one materials are found, an error is raised. Args: print_info: Show detailed info. **kwargs: Filter criteria. Returns: Dataframe containing materials with requested qualities. Possibly empty. Raises: LookupError: Requested material not found in database. LookupError: Too many results. """ df = find_materials(**kwargs) num_results = len(df) if num_results == 0: raise LookupError("Requested material not found in database.") elif num_results > 1: # list all possible choice error_string = ( "Too many results. Please refine your search.\n" + "Avilable materials based on request:\n" ) for _row, material in df.iterrows(): error_string += _describe_material(material) raise LookupError(error_string) else: material = df.iloc[0] if print_info: print("Found material in database.") print(_describe_material(material)) return material def _get_cif( short_label: str | None = None, chemical_formula: str | None = None, space_group_name: str | None = None, space_group_number: int | None = None, cell_length_a: float | None = None, cell_length_b: float | None = None, cell_length_c: float | None = None, cell_angle_alpha: float | None = None, cell_angle_beta: float | None = None, cell_angle_gamma: float | None = None, cell_volume: float | None = None, ICSD_label: str | None = None, OQMD_label: str | None = None, print_info: bool = False, outdir: str | pathlib.Path = "out", ) -> None: """Load cif and move it to the directory `outdir`. Args: short_label: Chemical formula and space group number separated by a hyphen "-". chemical_formula: Chemical formula. space_group_name: Space group name. space_group_number: Space group number. cell_length_a: Cell length a. cell_length_b: Cell length b. cell_length_c: Cell length c. cell_angle_alpha: Cell angle alpha. cell_angle_beta: Cell angle beta. cell_angle_gamma: Cell angle gamma. cell_volume: Cell volume. ICSD_label: Label in the NIST Inorganic Crystal Structure Database. OQMD_label: Label in the the Open Quantum Materials Database. print_info: Print info outdir: Output directory """ pathlib.Path(outdir).mkdir(exist_ok=True, parents=True) if short_label is not None: chemical_formula, space_group_number = _check_short_label(short_label) material = _find_unique_material( print_info=print_info, chemical_formula=chemical_formula, space_group_name=space_group_name, space_group_number=space_group_number, cell_length_a=cell_length_a, cell_length_b=cell_length_b, cell_length_c=cell_length_c, cell_angle_alpha=cell_angle_alpha, cell_angle_beta=cell_angle_beta, cell_angle_gamma=cell_angle_gamma, cell_volume=cell_volume, ICSD_label=ICSD_label, OQMD_label=OQMD_label, ) shutil.copy( DATA_DIR / material.label / "structure.cif", outdir, ) def _get_dft_output( short_label: str | None = None, chemical_formula: str | None = None, space_group_name: str | None = None, space_group_number: int | None = None, cell_length_a: float | None = None, cell_length_b: float | None = None, cell_length_c: float | None = None, cell_angle_alpha: float | None = None, cell_angle_beta: float | None = None, cell_angle_gamma: float | None = None, cell_volume: float | None = None, ICSD_label: str | None = None, OQMD_label: str | None = None, print_info: bool = False, outdir: str | pathlib.Path = "out", ) -> None: """Load dft output files and move them to directory `outdir`. Args: short_label: Chemical formula and space group number separated by a hyphen "-". chemical_formula: Chemical formula. space_group_name: Space group name. space_group_number: Space group number. cell_length_a: Cell length a. cell_length_b: Cell length b. cell_length_c: Cell length c. cell_angle_alpha: Cell angle alpha. cell_angle_beta: Cell angle beta. cell_angle_gamma: Cell angle gamma. cell_volume: Cell volume. ICSD_label: Label in the NIST Inorganic Crystal Structure Database. OQMD_label: Label in the the Open Quantum Materials Database. print_info: Print info outdir: Output directory """ pathlib.Path(outdir).mkdir(exist_ok=True, parents=True) if short_label is not None: chemical_formula, space_group_number = _check_short_label(short_label) material = _find_unique_material( print_info=print_info, chemical_formula=chemical_formula, space_group_name=space_group_name, space_group_number=space_group_number, cell_length_a=cell_length_a, cell_length_b=cell_length_b, cell_length_c=cell_length_c, cell_angle_alpha=cell_angle_alpha, cell_angle_beta=cell_angle_beta, cell_angle_gamma=cell_angle_gamma, cell_volume=cell_volume, ICSD_label=ICSD_label, OQMD_label=OQMD_label, ) for file in "jfile", "momfile", "posfile": shutil.copy( DATA_DIR / material.label / file, outdir, ) def _describe_material( material: pd.DataFrame | None = None, material_label: str | None = None ) -> str: """Describe material in a complete way. This function returns a string listing the properties of the given material or the given material label. Args: material: Material dataframe containing structure information. material_label: Label of material in local database. Returns: Description of the material Raises: ValueError: If `material` and `material_label` are both ``None``. """ if material is None and material_label is None: raise ValueError("Material and material label cannot be both empty.") elif material_label is not None: df = find_materials() material = df[df["label"] == material_label].iloc[0] return dedent( f""" Chemical Formula: {material.chemical_formula} Space group name: {material.space_group_name} Space group number: {material.space_group_number} Cell length a: {material.cell_length_a} Cell length b: {material.cell_length_b} Cell length c: {material.cell_length_c} Cell angle alpha: {material.cell_angle_alpha} Cell angle beta: {material.cell_angle_beta} Cell angle gamma: {material.cell_angle_gamma} Cell volume: {material.cell_volume} ICSD_label: {material.ICSD_label} OQMD_label: {material.OQMD_label} """ )