Skip to content

Commit 480f75e

Browse files
tpurcell90JaGeo
andauthored
Aims magnetic ordering (#922)
* Add tests for magnetism The Base FHI-aims schemas will also have to be modified * ADd magnetism job for FHI-aims * Modify the Aims Schemas to run with magnetism workflows * Add magnetic ordering support for FHI-aims This is approximate as magnetic properties in aims are only done approximately * Add magnetic ordering jobs Facilitates calculations via making default InputSet Generators * Update to pymatgen 2024.9.10 for new aims interface * fix lint error * Default phonon tests no longer needed with latest pymatge * Remove properties in ASE jobs test output.structure.properties is filled, fcc_ne_structure does not the equality assertion is therefore false * Fix ASE jobs test * Update src/atomate2/aims/schemas/calculation.py Co-authored-by: J. George <[email protected]> --------- Co-authored-by: J. George <[email protected]>
1 parent 4944aed commit 480f75e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+201644
-59
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Define Makers for Magnetic ordering flow in FHI-aims."""
2+
3+
from __future__ import annotations
4+
5+
from dataclasses import dataclass, field
6+
from typing import TYPE_CHECKING
7+
8+
from pymatgen.io.aims.sets.magnetism import (
9+
MagneticRelaxSetGenerator,
10+
MagneticStaticSetGenerator,
11+
)
12+
13+
if TYPE_CHECKING:
14+
from pymatgen.io.aims.sets.base import AimsInputGenerator
15+
16+
17+
from atomate2.aims.jobs.core import RelaxMaker, StaticMaker
18+
19+
20+
@dataclass
21+
class MagneticStaticMaker(StaticMaker):
22+
"""Maker to create FHI-aims SCF jobs.
23+
24+
Parameters
25+
----------
26+
calc_type: str
27+
The type key for the calculation
28+
name: str
29+
The job name
30+
input_set_generator: .AimsInputGenerator
31+
The InputGenerator for the calculation
32+
"""
33+
34+
calc_type: str = "magnetic_scf"
35+
name: str = "Magnetic SCF Calculation"
36+
input_set_generator: AimsInputGenerator = field(
37+
default_factory=MagneticStaticSetGenerator
38+
)
39+
40+
41+
@dataclass
42+
class MagneticRelaxMaker(RelaxMaker):
43+
"""Maker to create relaxation calculations.
44+
45+
Parameters
46+
----------
47+
calc_type: str
48+
The type key for the calculation
49+
name: str
50+
The job name
51+
input_set_generator: .AimsInputGenerator
52+
The InputGenerator for the calculation
53+
"""
54+
55+
calc_type: str = "relax"
56+
name: str = "Magnetic Relaxation calculation"
57+
input_set_generator: AimsInputGenerator = field(
58+
default_factory=MagneticRelaxSetGenerator
59+
)
60+
61+
@classmethod
62+
def fixed_cell_relaxation(cls, *args, **kwargs) -> RelaxMaker:
63+
"""Create a fixed cell relaxation maker."""
64+
return cls(
65+
input_set_generator=MagneticRelaxSetGenerator(
66+
*args, relax_cell=False, **kwargs
67+
),
68+
name=cls.name + " (fixed cell)",
69+
)
70+
71+
@classmethod
72+
def full_relaxation(cls, *args, **kwargs) -> RelaxMaker:
73+
"""Create a full relaxation maker."""
74+
return cls(
75+
input_set_generator=MagneticRelaxSetGenerator(
76+
*args, relax_cell=True, **kwargs
77+
)
78+
)

src/atomate2/aims/schemas/calculation.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import json
56
import os
67
from collections.abc import Sequence
78
from datetime import datetime, timezone
@@ -15,6 +16,7 @@
1516
from pymatgen.core import Molecule, Structure
1617
from pymatgen.core.trajectory import Trajectory
1718
from pymatgen.electronic_structure.dos import Dos
19+
from pymatgen.io.aims.inputs import AimsGeometryIn
1820
from pymatgen.io.aims.outputs import AimsOutput
1921
from pymatgen.io.common import VolumetricData
2022
from typing_extensions import Self
@@ -185,6 +187,25 @@ def from_aims_output(
185187
)
186188

187189

190+
class CalculationInput(BaseModel):
191+
"""The FHI-aims Calculation input doc.
192+
193+
Parameters
194+
----------
195+
structure: Structure or Molecule
196+
The input pymatgen Structure or Molecule of the system
197+
parameters: dict[str, Any]
198+
The parameters passed in the control.in file
199+
"""
200+
201+
structure: Union[Structure, Molecule] = Field(
202+
None, description="The input structure object"
203+
)
204+
parameters: dict[str, Any] = Field(
205+
{}, description="The input parameters for FHI-aims"
206+
)
207+
208+
188209
class Calculation(BaseModel):
189210
"""Full FHI-aims calculation inputs and outputs.
190211
@@ -198,6 +219,8 @@ class Calculation(BaseModel):
198219
Whether FHI-aims completed the calculation successfully
199220
output: .CalculationOutput
200221
The FHI-aims calculation output
222+
input: .CalculationInput
223+
The FHI-aims calculation input
201224
completed_at: str
202225
Timestamp for when the calculation was completed
203226
output_file_paths: Dict[str, str]
@@ -214,6 +237,10 @@ class Calculation(BaseModel):
214237
has_aims_completed: TaskState = Field(
215238
None, description="Whether FHI-aims completed the calculation successfully"
216239
)
240+
completed: bool = Field(
241+
None, description="Whether FHI-aims completed the calculation successfully"
242+
)
243+
input: CalculationInput = Field(None, description="The FHI-aims calculation input")
217244
output: CalculationOutput = Field(
218245
None, description="The FHI-aims calculation output"
219246
)
@@ -236,7 +263,6 @@ def from_aims_files(
236263
parse_dos: str | bool = False,
237264
parse_bandstructure: str | bool = False,
238265
store_trajectory: bool = False,
239-
# store_scf: bool = False,
240266
store_volumetric_data: Optional[Sequence[str]] = STORE_VOLUMETRIC_DATA,
241267
) -> tuple[Self, dict[AimsObject, dict]]:
242268
"""Create an FHI-aims calculation document from a directory and file paths.
@@ -289,6 +315,15 @@ def from_aims_files(
289315
aims_output_file = dir_name / aims_output_file
290316

291317
volumetric_files = [] if volumetric_files is None else volumetric_files
318+
319+
aims_geo_in = AimsGeometryIn.from_file(dir_name / "geometry.in")
320+
aims_parameters = {}
321+
with open(str(dir_name / "parameters.json")) as pj:
322+
aims_parameters = json.load(pj)
323+
324+
input_doc = CalculationInput(
325+
structure=aims_geo_in.structure, parameters=aims_parameters
326+
)
292327
aims_output = AimsOutput.from_outfile(aims_output_file)
293328

294329
completed_at = str(
@@ -323,8 +358,10 @@ def from_aims_files(
323358
task_name=task_name,
324359
aims_version=aims_output.aims_version,
325360
has_aims_completed=has_aims_completed,
361+
completed=has_aims_completed == TaskState.SUCCESS,
326362
completed_at=completed_at,
327363
output=output_doc,
364+
input=input_doc,
328365
output_file_paths={k.name.lower(): v for k, v in output_file_paths.items()},
329366
)
330367

src/atomate2/aims/schemas/task.py

Lines changed: 37 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import numpy as np
1212
from emmet.core.math import Matrix3D, Vector3D
1313
from emmet.core.structure import MoleculeMetadata, StructureMetadata
14+
from emmet.core.task import BaseTaskDocument
1415
from emmet.core.tasks import get_uri
16+
from emmet.core.vasp.calc_types.enums import TaskType
1517
from pydantic import BaseModel, Field
1618
from pymatgen.core import Molecule, Structure
1719
from pymatgen.entries.computed_entries import ComputedEntry
@@ -78,56 +80,6 @@ def from_aims_calc_docs(cls, calc_docs: list[Calculation]) -> Self:
7880
)
7981

8082

81-
class Species(BaseModel):
82-
"""A representation of the most important information about each type of species.
83-
84-
Parameters
85-
----------
86-
element: str
87-
Element assigned to this atom kind
88-
species_defaults: str
89-
Basis set for this atom kind
90-
"""
91-
92-
element: str = Field(None, description="Element assigned to this atom kind")
93-
species_defaults: str = Field(None, description="Basis set for this atom kind")
94-
95-
96-
class SpeciesSummary(BaseModel):
97-
"""A summary of species defaults.
98-
99-
Parameters
100-
----------
101-
species_defaults: Dict[str, .Species]
102-
Dictionary mapping atomic kind labels to their info
103-
"""
104-
105-
species_defaults: dict[str, Species] = Field(
106-
None, description="Dictionary mapping atomic kind labels to their info"
107-
)
108-
109-
@classmethod
110-
def from_species_info(cls, species_info: dict[str, dict[str, Any]]) -> Self:
111-
"""Initialize from the atomic_kind_info dictionary.
112-
113-
Parameters
114-
----------
115-
species_info: Dict[str, Dict[str, Any]]
116-
The information for the basis set for the calculation
117-
118-
Returns
119-
-------
120-
The SpeciesSummary
121-
"""
122-
dct: dict[str, dict[str, Any]] = {"species_defaults": {}}
123-
for kind, info in species_info.items():
124-
dct["species_defaults"][kind] = {
125-
"element": info["element"],
126-
"species_defaults": info["species_defaults"],
127-
}
128-
return cls(**dct)
129-
130-
13183
class InputDoc(BaseModel):
13284
"""Summary of inputs for an FHI-aims calculation.
13385
@@ -137,19 +89,24 @@ class InputDoc(BaseModel):
13789
The input pymatgen Structure or Molecule of the system
13890
species_info: .SpeciesSummary
13991
Summary of the species defaults used for each atom kind
92+
parameters: dict[str, Any]
93+
The parameters passed in the control.in file
14094
xc: str
14195
Exchange-correlation functional used if not the default
14296
"""
14397

14498
structure: Union[Structure, Molecule] = Field(
14599
None, description="The input structure object"
146100
)
147-
species_info: SpeciesSummary = Field(
148-
None, description="Summary of the species defaults used for each atom kind"
101+
parameters: dict[str, Any] = Field(
102+
{}, description="The input parameters for FHI-aims"
149103
)
150104
xc: str = Field(
151105
None, description="Exchange-correlation functional used if not the default"
152106
)
107+
magnetic_moments: Optional[list[float]] = Field(
108+
None, description="Magnetic moments for each atom"
109+
)
153110

154111
@classmethod
155112
def from_aims_calc_doc(cls, calc_doc: Calculation) -> Self:
@@ -165,12 +122,16 @@ def from_aims_calc_doc(cls, calc_doc: Calculation) -> Self:
165122
.InputDoc
166123
A summary of the input structure and parameters.
167124
"""
168-
summary = SpeciesSummary.from_species_info(calc_doc.input.species_info)
125+
structure = calc_doc.input.structure
126+
magnetic_moments = None
127+
if "magmom" in structure.site_properties:
128+
magnetic_moments = structure.site_properties["magmom"]
169129

170130
return cls(
171-
structure=calc_doc.input.structure,
172-
atomic_kind_info=summary,
173-
xc=str(calc_doc.run_type),
131+
structure=structure,
132+
parameters=calc_doc.input.parameters,
133+
xc=calc_doc.input.parameters["xc"],
134+
magnetic_moments=magnetic_moments,
174135
)
175136

176137

@@ -383,15 +344,19 @@ def from_data(
383344
)
384345

385346

386-
class AimsTaskDoc(StructureMetadata, MoleculeMetadata):
347+
class AimsTaskDoc(BaseTaskDocument, StructureMetadata, MoleculeMetadata):
387348
"""Definition of FHI-aims task document.
388349
389350
Parameters
390351
----------
352+
calc_code: str
353+
The calculation code used to compute the task
391354
dir_name: str
392355
The directory for this FHI-aims task
393356
last_updated: str
394357
Timestamp for this task document was last updated
358+
completed: bool
359+
Whether this calculation completed
395360
completed_at: str
396361
Timestamp for when this task was completed
397362
input: .InputDoc
@@ -430,11 +395,13 @@ class AimsTaskDoc(StructureMetadata, MoleculeMetadata):
430395
Additional json loaded from the calculation directory
431396
"""
432397

398+
calc_code: str = "aims"
433399
dir_name: str = Field(None, description="The directory for this FHI-aims task")
434400
last_updated: str = Field(
435401
default_factory=datetime_str,
436402
description="Timestamp for this task document was last updated",
437403
)
404+
completed: bool = Field(None, description="Whether this calculation completed")
438405
completed_at: str = Field(
439406
None, description="Timestamp for when this task was completed"
440407
)
@@ -553,7 +520,9 @@ def from_directory(
553520
"calcs_reversed": calcs_reversed,
554521
"analysis": analysis,
555522
"tags": tags,
523+
"completed": calcs_reversed[-1].completed,
556524
"completed_at": calcs_reversed[-1].completed_at,
525+
"input": InputDoc.from_aims_calc_doc(calcs_reversed[-1]),
557526
"output": OutputDoc.from_aims_calc_doc(calcs_reversed[-1]),
558527
"state": _get_state(calcs_reversed, analysis),
559528
"entry": cls.get_entry(calcs_reversed),
@@ -597,6 +566,17 @@ def get_entry(
597566
}
598567
return ComputedEntry.from_dict(entry_dict)
599568

569+
# TARP: This is done because the mangnetism schema assume that VASP
570+
# TaskTypes are used. I think this should be changed, but that
571+
# would require modifications in emmet
572+
@property
573+
def task_type(self) -> TaskType:
574+
"""Get the task type of the calculation."""
575+
if "Relaxation calculation" in self.task_label:
576+
return TaskType("Structure Optimization")
577+
578+
return TaskType("Static")
579+
600580

601581
def _find_aims_files(
602582
path: Path | str,

src/atomate2/common/jobs/magnetism.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ def enumerate_magnetic_orderings(
7979
truncate_by_symmetry=truncate_by_symmetry,
8080
transformation_kwargs=transformation_kwargs,
8181
)
82-
8382
return enumerator.ordered_structures, enumerator.ordered_structure_origins
8483

8584

0 commit comments

Comments
 (0)