Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aims magnetic ordering #922

Merged
merged 18 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/atomate2/aims/jobs/magnetism.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Define Makers for Magnetic ordering flow in FHI-aims."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from pymatgen.io.aims.sets.magnetism import (
MagneticRelaxSetGenerator,
MagneticStaticSetGenerator,
)

if TYPE_CHECKING:
from pymatgen.io.aims.sets.base import AimsInputGenerator


from atomate2.aims.jobs.core import RelaxMaker, StaticMaker


@dataclass
class MagneticStaticMaker(StaticMaker):
"""Maker to create FHI-aims SCF jobs.
Parameters
----------
calc_type: str
The type key for the calculation
name: str
The job name
input_set_generator: .AimsInputGenerator
The InputGenerator for the calculation
"""

calc_type: str = "magnetic_scf"
name: str = "Magnetic SCF Calculation"
input_set_generator: AimsInputGenerator = field(
default_factory=MagneticStaticSetGenerator
)


@dataclass
class MagneticRelaxMaker(RelaxMaker):
"""Maker to create relaxation calculations.
Parameters
----------
calc_type: str
The type key for the calculation
name: str
The job name
input_set_generator: .AimsInputGenerator
The InputGenerator for the calculation
"""

calc_type: str = "relax"
name: str = "Magnetic Relaxation calculation"
input_set_generator: AimsInputGenerator = field(
default_factory=MagneticRelaxSetGenerator
)

@classmethod
def fixed_cell_relaxation(cls, *args, **kwargs) -> RelaxMaker:
"""Create a fixed cell relaxation maker."""
return cls(
input_set_generator=MagneticRelaxSetGenerator(
*args, relax_cell=False, **kwargs
),
name=cls.name + " (fixed cell)",
)

@classmethod
def full_relaxation(cls, *args, **kwargs) -> RelaxMaker:
"""Create a full relaxation maker."""
return cls(
input_set_generator=MagneticRelaxSetGenerator(
*args, relax_cell=True, **kwargs
)
)
39 changes: 38 additions & 1 deletion src/atomate2/aims/schemas/calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import json
import os
from collections.abc import Sequence
from datetime import datetime, timezone
Expand All @@ -15,6 +16,7 @@
from pymatgen.core import Molecule, Structure
from pymatgen.core.trajectory import Trajectory
from pymatgen.electronic_structure.dos import Dos
from pymatgen.io.aims.inputs import AimsGeometryIn
from pymatgen.io.aims.outputs import AimsOutput
from pymatgen.io.common import VolumetricData
from typing_extensions import Self
Expand Down Expand Up @@ -185,6 +187,25 @@ def from_aims_output(
)


class CalculationInput(BaseModel):
"""The FHI-aims Calculation input doc.

Parameters
----------
structure: Structure or Molecule
The input pymatgen Structure or Molecule of the system
parameters: dict[str, Any]
The parameters passed in the control.in file
"""

structure: Union[Structure, Molecule] = Field(
None, description="The input structure object"
)
parameters: dict[str, Any] = Field(
{}, description="The input parameters for FHI-aims"
)


class Calculation(BaseModel):
"""Full FHI-aims calculation inputs and outputs.

Expand All @@ -198,6 +219,8 @@ class Calculation(BaseModel):
Whether FHI-aims completed the calculation successfully
output: .CalculationOutput
The FHI-aims calculation output
input: .CalculationOutput
tpurcell90 marked this conversation as resolved.
Show resolved Hide resolved
The FHI-aims calculation input
completed_at: str
Timestamp for when the calculation was completed
output_file_paths: Dict[str, str]
Expand All @@ -214,6 +237,10 @@ class Calculation(BaseModel):
has_aims_completed: TaskState = Field(
None, description="Whether FHI-aims completed the calculation successfully"
)
completed: bool = Field(
None, description="Whether FHI-aims completed the calculation successfully"
)
input: CalculationInput = Field(None, description="The FHI-aims calculation input")
output: CalculationOutput = Field(
None, description="The FHI-aims calculation output"
)
Expand All @@ -236,7 +263,6 @@ def from_aims_files(
parse_dos: str | bool = False,
parse_bandstructure: str | bool = False,
store_trajectory: bool = False,
# store_scf: bool = False,
store_volumetric_data: Optional[Sequence[str]] = STORE_VOLUMETRIC_DATA,
) -> tuple[Self, dict[AimsObject, dict]]:
"""Create an FHI-aims calculation document from a directory and file paths.
Expand Down Expand Up @@ -289,6 +315,15 @@ def from_aims_files(
aims_output_file = dir_name / aims_output_file

volumetric_files = [] if volumetric_files is None else volumetric_files

aims_geo_in = AimsGeometryIn.from_file(dir_name / "geometry.in")
aims_parameters = {}
with open(str(dir_name / "parameters.json")) as pj:
aims_parameters = json.load(pj)

input_doc = CalculationInput(
structure=aims_geo_in.structure, parameters=aims_parameters
)
aims_output = AimsOutput.from_outfile(aims_output_file)

completed_at = str(
Expand Down Expand Up @@ -323,8 +358,10 @@ def from_aims_files(
task_name=task_name,
aims_version=aims_output.aims_version,
has_aims_completed=has_aims_completed,
completed=has_aims_completed == TaskState.SUCCESS,
completed_at=completed_at,
output=output_doc,
input=input_doc,
output_file_paths={k.name.lower(): v for k, v in output_file_paths.items()},
)

Expand Down
94 changes: 37 additions & 57 deletions src/atomate2/aims/schemas/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import numpy as np
from emmet.core.math import Matrix3D, Vector3D
from emmet.core.structure import MoleculeMetadata, StructureMetadata
from emmet.core.task import BaseTaskDocument
from emmet.core.tasks import get_uri
from emmet.core.vasp.calc_types.enums import TaskType
from pydantic import BaseModel, Field
from pymatgen.core import Molecule, Structure
from pymatgen.entries.computed_entries import ComputedEntry
Expand Down Expand Up @@ -78,56 +80,6 @@ def from_aims_calc_docs(cls, calc_docs: list[Calculation]) -> Self:
)


class Species(BaseModel):
"""A representation of the most important information about each type of species.
Parameters
----------
element: str
Element assigned to this atom kind
species_defaults: str
Basis set for this atom kind
"""

element: str = Field(None, description="Element assigned to this atom kind")
species_defaults: str = Field(None, description="Basis set for this atom kind")


class SpeciesSummary(BaseModel):
"""A summary of species defaults.
Parameters
----------
species_defaults: Dict[str, .Species]
Dictionary mapping atomic kind labels to their info
"""

species_defaults: dict[str, Species] = Field(
None, description="Dictionary mapping atomic kind labels to their info"
)

@classmethod
def from_species_info(cls, species_info: dict[str, dict[str, Any]]) -> Self:
"""Initialize from the atomic_kind_info dictionary.
Parameters
----------
species_info: Dict[str, Dict[str, Any]]
The information for the basis set for the calculation
Returns
-------
The SpeciesSummary
"""
dct: dict[str, dict[str, Any]] = {"species_defaults": {}}
for kind, info in species_info.items():
dct["species_defaults"][kind] = {
"element": info["element"],
"species_defaults": info["species_defaults"],
}
return cls(**dct)


class InputDoc(BaseModel):
"""Summary of inputs for an FHI-aims calculation.
Expand All @@ -137,19 +89,24 @@ class InputDoc(BaseModel):
The input pymatgen Structure or Molecule of the system
species_info: .SpeciesSummary
Summary of the species defaults used for each atom kind
parameters: dict[str, Any]
The parameters passed in the control.in file
xc: str
Exchange-correlation functional used if not the default
"""

structure: Union[Structure, Molecule] = Field(
None, description="The input structure object"
)
species_info: SpeciesSummary = Field(
None, description="Summary of the species defaults used for each atom kind"
parameters: dict[str, Any] = Field(
{}, description="The input parameters for FHI-aims"
)
xc: str = Field(
None, description="Exchange-correlation functional used if not the default"
)
magnetic_moments: Optional[list[float]] = Field(
None, description="Magnetic moments for each atom"
)

@classmethod
def from_aims_calc_doc(cls, calc_doc: Calculation) -> Self:
Expand All @@ -165,12 +122,16 @@ def from_aims_calc_doc(cls, calc_doc: Calculation) -> Self:
.InputDoc
A summary of the input structure and parameters.
"""
summary = SpeciesSummary.from_species_info(calc_doc.input.species_info)
structure = calc_doc.input.structure
magnetic_moments = None
if "magmom" in structure.site_properties:
magnetic_moments = structure.site_properties["magmom"]

return cls(
structure=calc_doc.input.structure,
atomic_kind_info=summary,
xc=str(calc_doc.run_type),
structure=structure,
parameters=calc_doc.input.parameters,
xc=calc_doc.input.parameters["xc"],
magnetic_moments=magnetic_moments,
)


Expand Down Expand Up @@ -383,15 +344,19 @@ def from_data(
)


class AimsTaskDoc(StructureMetadata, MoleculeMetadata):
class AimsTaskDoc(BaseTaskDocument, StructureMetadata, MoleculeMetadata):
"""Definition of FHI-aims task document.
Parameters
----------
calc_code: str
The calculation code used to compute the task
dir_name: str
The directory for this FHI-aims task
last_updated: str
Timestamp for this task document was last updated
completed: bool
Whether this calculation completed
completed_at: str
Timestamp for when this task was completed
input: .InputDoc
Expand Down Expand Up @@ -430,11 +395,13 @@ class AimsTaskDoc(StructureMetadata, MoleculeMetadata):
Additional json loaded from the calculation directory
"""

calc_code: str = "aims"
dir_name: str = Field(None, description="The directory for this FHI-aims task")
last_updated: str = Field(
default_factory=datetime_str,
description="Timestamp for this task document was last updated",
)
completed: bool = Field(None, description="Whether this calculation completed")
completed_at: str = Field(
None, description="Timestamp for when this task was completed"
)
Expand Down Expand Up @@ -553,7 +520,9 @@ def from_directory(
"calcs_reversed": calcs_reversed,
"analysis": analysis,
"tags": tags,
"completed": calcs_reversed[-1].completed,
"completed_at": calcs_reversed[-1].completed_at,
"input": InputDoc.from_aims_calc_doc(calcs_reversed[-1]),
"output": OutputDoc.from_aims_calc_doc(calcs_reversed[-1]),
"state": _get_state(calcs_reversed, analysis),
"entry": cls.get_entry(calcs_reversed),
Expand Down Expand Up @@ -597,6 +566,17 @@ def get_entry(
}
return ComputedEntry.from_dict(entry_dict)

# TARP: This is done because the mangnetism schema assume that VASP
# TaskTypes are used. I think this should be changed, but that
# would require modifications in emmet
@property
def task_type(self) -> TaskType:
"""Get the task type of the calculation."""
if "Relaxation calculation" in self.task_label:
return TaskType("Structure Optimization")

return TaskType("Static")


def _find_aims_files(
path: Path | str,
Expand Down
1 change: 0 additions & 1 deletion src/atomate2/common/jobs/magnetism.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ def enumerate_magnetic_orderings(
truncate_by_symmetry=truncate_by_symmetry,
transformation_kwargs=transformation_kwargs,
)

return enumerator.ordered_structures, enumerator.ordered_structure_origins


Expand Down
Loading
Loading