"""Flows for calculating magnetic orderings and other magnetism-related tasks."""
from __future__ import annotations
import warnings
from abc import ABC
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Literal
from jobflow import Flow, Maker
from pymatgen.core import Element
from atomate2.common.jobs.magnetism import (
enumerate_magnetic_orderings,
postprocess_orderings,
run_ordering_calculations,
)
from atomate2.vasp.jobs.core import RelaxMaker, StaticMaker
from atomate2.vasp.sets.core import StaticSetGenerator
if TYPE_CHECKING:
from collections.abc import Sequence
from pymatgen.core.structure import Structure
__all__ = ["MagneticOrderingsMaker"]
[docs]
@dataclass
class MagneticOrderingsMaker(Maker, ABC):
"""Maker to calculate possible collinear magnetic orderings for a material.
Given an input structure, possible magnetic orderings will be enumerated and ranked
based on symmetry up to a maximum number of orderings. Each ordering will be
optionally relaxed and a higher quality static calculation performed to obtain a
total energy. The lowest energy ordering is the predicted ground-state collinear
ordering.
This Maker can be trivially implemented for your DFT code of choice by
utilizing the appropriate static/relax makers. However, for postprocessing to
work correctly, one must ensure that the calculation outputs can be processed into a
MagneticOrderingsDocument. As the TaskDoc class is currently defined only for
VASP, one should ensure that the task document returned during their DFT runs
contains the necessary parameters (e.g., TaskDoc.input.magnetic_moments).
This warning will be removed once a universal TaskDoc is implemented (Issue #741).
This workflow was benchmarked with VASP for a wide range of test materials and
originally implemented in atomate (v1) for VASP as the MagneticOrderingsWF. Please
refer to the following paper for more information and cite appropriately:
Horton, M.K., Montoya, J.H., Liu, M. et al. High-throughput prediction of the
ground-state collinear magnetic order of inorganic materials using Density
Functional Theory. npj Computational Materials 5, 64 (2019).
https://doi.org/10.1038/s41524-019-0199-7
.. Note::
Good performance of this workflow is ultimately dependent on an appropriate
choice of Hubbard U, Hund J values and/or the functional. The defaults will work
well for many transition metal oxides.
Parameters
----------
name : str
Name of the flows produced by this Maker.
static_maker : Maker
Maker used to perform static calculations for total energy. VASP is selected as
the default DFT code (atomate2.vasp.jobs.StaticMaker).
relax_maker : Maker | None
Maker used to perform relaxations of the enumerated structures. VASP is selected
as the default DFT code (atomate2.vasp.jobs.RelaxMaker). If this field is None,
relaxations will be skipped (i.e., only static calculations are performed).
default_magmoms : dict | None
Optional default mapping of magnetic elements to their initial magnetic moments
in μB. Generally these are chosen to be high-spin, since they can relax to a
low-spin configuration during a DFT electronic configuration. If None, will use
the default values provided in pymatgen/analysis/magnetism/default_magmoms.yaml.
strategies : tuple[str]
Different ordering strategies to use. Choose from ferromagnetic,
antiferromagnetic, antiferromagnetic_by_motif, ferrimagnetic_by_motif,
ferrimagnetic_by_species, or nonmagnetic. Here, "motif", means to use a
different ordering parameter for symmetry inequivalent sites.
automatic : bool
If True, will automatically choose sensible strategies. Defaults to True.
truncate_by_symmetry : bool
If True, will remove very unsymmetrical orderings that are likely physically
implausible. Defaults to True.
transformation_kwargs : dict | None
Keyword arguments provided to MagOrderingTransformation in pymatgen. Defaults to
None.
"""
name: str = "magnetic_orderings"
static_maker: Maker = field(
default_factory=lambda: StaticMaker(
input_set_generator=StaticSetGenerator(user_incar_settings={"EDIFF": 1e-7})
)
)
relax_maker: Maker | None = field(default_factory=RelaxMaker)
default_magmoms: dict[Element, float] | None = None
strategies: Sequence[
Literal[
"ferromagnetic",
"antiferromagnetic",
"antiferromagnetic_by_motif",
"ferrimagnetic_by_motif",
"ferrimagnetic_by_species",
"nonmagnetic",
]
] = ("ferromagnetic", "antiferromagnetic")
automatic: bool = True
truncate_by_symmetry: bool = True
transformation_kwargs: dict | None = None
def __post_init__(self) -> None:
"""Ensure that the static and relax makers come from the same base maker.
This ensures that the same DFT code is used for both calculations.
"""
if self.relax_maker is None:
warnings.warn(
"No relax_maker provided, relaxations will be skipped. Please be"
" sure that this is intended!",
stacklevel=2,
)
else:
static_base_maker_name = type(self.static_maker).__mro__[1].__name__
relax_base_maker_name = type(self.relax_maker).__mro__[1].__name__
if relax_base_maker_name != static_base_maker_name:
warnings.warn(
"The provided static and relax makers do not use the "
"same DFT code! Please check the base maker used.",
stacklevel=2,
)
[docs]
def make(
self,
structure: Structure,
) -> Flow:
"""Make a flow to calculate collinear magnetic orderings for a given structure.
Parameters
----------
structure : Structure
A pymatgen structure object.
Returns
-------
flow: Flow
The magnetic ordering workflow.
"""
jobs = []
if Element("Co") in structure.elements:
warnings.warn(
(
"Co detected in structure! Please consider testing both low-spin"
" and high-spin configurations. The current default for Co (without"
" oxidation state) is high-spin. Refer to the defaults in"
" pymatgen/analysis/magnetism/default_magmoms.yaml for more"
" information. "
),
stacklevel=2,
)
orderings = enumerate_magnetic_orderings(
structure,
default_magmoms=self.default_magmoms,
strategies=self.strategies,
automatic=self.automatic,
truncate_by_symmetry=self.truncate_by_symmetry,
transformation_kwargs=self.transformation_kwargs,
)
calculations = run_ordering_calculations(
orderings.output, # pylint: disable=no-member
static_maker=self.static_maker,
relax_maker=self.relax_maker,
)
postprocessing = postprocess_orderings(calculations.output)
jobs = [orderings, calculations, postprocessing]
return Flow(
jobs=jobs,
output=postprocessing.output,
name=f"{self.name} ({structure.composition.reduced_formula})",
)