Skip to content

Commit 880028d

Browse files
committed
output ForceFieldStructureTaskDocument or ForceFieldMoleculeTaskDocument based on type of mol_or_struct
1 parent df55888 commit 880028d

File tree

4 files changed

+113
-31
lines changed

4 files changed

+113
-31
lines changed

src/atomate2/ase/schemas.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -233,26 +233,6 @@ class AseStructureTaskDoc(StructureMetadata):
233233

234234
tags: Optional[list[str]] = Field(None, description="List of tags for the task.")
235235

236-
@classmethod
237-
def from_ase_task_doc(
238-
cls, ase_task_doc: AseTaskDoc, **task_document_kwargs
239-
) -> AseStructureTaskDoc:
240-
"""Create an AseStructureTaskDoc for a task that has ASE-compatible outputs.
241-
242-
Parameters
243-
----------
244-
ase_task_doc : AseTaskDoc
245-
Task doc for the calculation
246-
task_document_kwargs : dict
247-
Additional keyword args passed to :obj:`.AseStructureTaskDoc()`.
248-
"""
249-
task_document_kwargs.update(
250-
{k: getattr(ase_task_doc, k) for k in _task_doc_translation_keys},
251-
structure=ase_task_doc.mol_or_struct,
252-
)
253-
return cls.from_structure(
254-
meta_structure=ase_task_doc.mol_or_struct, **task_document_kwargs
255-
)
256236

257237

258238
class AseMoleculeTaskDoc(MoleculeMetadata):

src/atomate2/forcefields/jobs.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from atomate2.ase.jobs import AseRelaxMaker
1717
from atomate2.forcefields import MLFF, _get_formatted_ff_name
18-
from atomate2.forcefields.schemas import ForceFieldStructureTaskDocument
18+
from atomate2.forcefields.schemas import ForceFieldStructureTaskDocument, ForceFieldMoleculeTaskDocument, ForceFieldTaskDocument
1919
from atomate2.forcefields.utils import ase_calculator, revert_default_dtype
2020

2121
if TYPE_CHECKING:
@@ -73,7 +73,7 @@ def make(structure):
7373
A decorated version of the make function that will generate forcefield jobs.
7474
"""
7575
return job(
76-
method, data=_FORCEFIELD_DATA_OBJECTS, output_schema=ForceFieldStructureTaskDocument
76+
method, data=_FORCEFIELD_DATA_OBJECTS
7777
)
7878

7979

@@ -147,7 +147,7 @@ def __post_init__(self) -> None:
147147
@forcefield_job
148148
def make(
149149
self, structure: Structure, prev_dir: str | Path | None = None
150-
) -> ForceFieldStructureTaskDocument:
150+
) -> Union[ForceFieldStructureTaskDocument, ForceFieldMoleculeTaskDocument]:
151151
"""
152152
Perform a relaxation of a structure using a force field.
153153
@@ -170,7 +170,7 @@ def make(
170170
stacklevel=1,
171171
)
172172

173-
return ForceFieldStructureTaskDocument.from_ase_compatible_result(
173+
return ForceFieldTaskDocument.from_ase_compatible_result(
174174
str(self.force_field_name), # make mypy happy
175175
ase_result,
176176
self.steps,

src/atomate2/forcefields/md.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
_DEFAULT_CALCULATOR_KWARGS,
1616
_FORCEFIELD_DATA_OBJECTS,
1717
)
18-
from atomate2.forcefields.schemas import ForceFieldStructureTaskDocument
18+
from atomate2.forcefields.schemas import ForceFieldStructureTaskDocument, ForceFieldMoleculeTaskDocument, ForceFieldTaskDocument
1919
from atomate2.forcefields.utils import ase_calculator, revert_default_dtype
2020

2121
if TYPE_CHECKING:
@@ -126,13 +126,12 @@ def __post_init__(self) -> None:
126126

127127
@job(
128128
data=[*_FORCEFIELD_DATA_OBJECTS, "ionic_steps"],
129-
output_schema=ForceFieldStructureTaskDocument,
130129
)
131130
def make(
132131
self,
133132
structure: Structure,
134133
prev_dir: str | Path | None = None,
135-
) -> ForceFieldStructureTaskDocument:
134+
) -> Union[ForceFieldStructureTaskDocument, ForceFieldMoleculeTaskDocument]:
136135
"""
137136
Perform MD on a structure using forcefields and jobflow.
138137
@@ -156,7 +155,7 @@ def make(
156155
stacklevel=1,
157156
)
158157

159-
return ForceFieldStructureTaskDocument.from_ase_compatible_result(
158+
return ForceFieldTaskDocument.from_ase_compatible_result(
160159
str(self.force_field_name), # make mypy happy
161160
md_result,
162161
relax_cell=(self.ensemble == MDEnsemble.npt),

src/atomate2/forcefields/schemas.py

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
from emmet.core.vasp.calculation import StoreTrajectoryOption
99
from monty.dev import deprecated
1010
from pydantic import Field
11-
from pymatgen.core import Structure
11+
from pymatgen.core import Structure, Molecule
1212

13-
from atomate2.ase.schemas import AseObject, AseResult, AseStructureTaskDoc, AseTaskDoc
13+
from atomate2.ase.schemas import AseObject, AseResult, AseMoleculeTaskDoc, AseStructureTaskDoc, AseTaskDoc
14+
from atomate2.ase.schemas import _task_doc_translation_keys
1415
from atomate2.forcefields import MLFF
1516

1617

@@ -67,6 +68,79 @@ class ForceFieldStructureTaskDocument(AseStructureTaskDoc):
6768
),
6869
)
6970

71+
72+
@property
73+
def forcefield_objects(self) -> Optional[dict[AseObject, Any]]:
74+
"""Alias `objects` attr for backwards compatibility."""
75+
return self.objects
76+
77+
class ForceFieldMoleculeTaskDocument(AseMoleculeTaskDoc):
78+
"""Document containing information on structure manipulation using a force field."""
79+
80+
forcefield_name: Optional[str] = Field(
81+
None,
82+
description="name of the interatomic potential used for relaxation.",
83+
)
84+
85+
forcefield_version: Optional[str] = Field(
86+
"Unknown",
87+
description="version of the interatomic potential used for relaxation.",
88+
)
89+
90+
dir_name: Optional[str] = Field(
91+
None, description="Directory where the force field calculations are performed."
92+
)
93+
94+
included_objects: Optional[list[AseObject]] = Field(
95+
None, description="list of forcefield objects included with this task document"
96+
)
97+
objects: Optional[dict[AseObject, Any]] = Field(
98+
None, description="Forcefield objects associated with this task"
99+
)
100+
101+
is_force_converged: Optional[bool] = Field(
102+
None,
103+
description=(
104+
"Whether the calculation is converged with respect to interatomic forces."
105+
),
106+
)
107+
108+
@property
109+
def forcefield_objects(self) -> Optional[dict[AseObject, Any]]:
110+
"""Alias `objects` attr for backwards compatibility."""
111+
return self.objects
112+
113+
class ForceFieldTaskDocument(AseTaskDoc):
114+
"""Document containing information on structure manipulation using a force field."""
115+
116+
forcefield_name: Optional[str] = Field(
117+
None,
118+
description="name of the interatomic potential used for relaxation.",
119+
)
120+
121+
forcefield_version: Optional[str] = Field(
122+
"Unknown",
123+
description="version of the interatomic potential used for relaxation.",
124+
)
125+
126+
dir_name: Optional[str] = Field(
127+
None, description="Directory where the force field calculations are performed."
128+
)
129+
130+
included_objects: Optional[list[AseObject]] = Field(
131+
None, description="list of forcefield objects included with this task document"
132+
)
133+
objects: Optional[dict[AseObject, Any]] = Field(
134+
None, description="Forcefield objects associated with this task"
135+
)
136+
137+
is_force_converged: Optional[bool] = Field(
138+
None,
139+
description=(
140+
"Whether the calculation is converged with respect to interatomic forces."
141+
),
142+
)
143+
70144
@classmethod
71145
def from_ase_compatible_result(
72146
cls,
@@ -87,7 +161,7 @@ def from_ase_compatible_result(
87161
store_trajectory: StoreTrajectoryOption = StoreTrajectoryOption.NO,
88162
tags: list[str] | None = None,
89163
**task_document_kwargs,
90-
) -> ForceFieldStructureTaskDocument:
164+
) -> Union[ForceFieldStructureTaskDocument, ForceFieldMoleculeTaskDocument]:
91165
"""Create an AseTaskDoc for a task that has ASE-compatible outputs.
92166
93167
Parameters
@@ -155,6 +229,35 @@ def from_ase_compatible_result(
155229

156230
return cls.from_ase_task_doc(ase_task_doc, **ff_kwargs)
157231

232+
@classmethod
233+
def from_ase_task_doc(
234+
cls, ase_task_doc: AseTaskDoc, **task_document_kwargs
235+
) -> Union[ForceFieldStructureTaskDocument, ForceFieldMoleculeTaskDocument]:
236+
"""Create an AseStructureTaskDoc for a task that has ASE-compatible outputs.
237+
238+
Parameters
239+
----------
240+
ase_task_doc : AseTaskDoc
241+
Task doc for the calculation
242+
task_document_kwargs : dict
243+
Additional keyword args passed to :obj:`.AseStructureTaskDoc()`.
244+
"""
245+
task_document_kwargs.update(
246+
{k: getattr(ase_task_doc, k) for k in _task_doc_translation_keys},
247+
)
248+
if isinstance(ase_task_doc.mol_or_struct, Structure):
249+
meta_class = ForceFieldStructureTaskDocument
250+
k = "structure"
251+
if relax_cell := getattr(ase_task_doc, "relax_cell", None):
252+
kwargs.update({"relax_cell": relax_cell})
253+
task_document_kwargs.update(structure=ase_task_doc.mol_or_struct)
254+
elif isinstance(ase_task_doc.mol_or_struct, Molecule):
255+
meta_class = ForceFieldMoleculeTaskDocument
256+
k = "molecule"
257+
task_document_kwargs.update(molecule=ase_task_doc.mol_or_struct)
258+
task_document_kwargs.update({k: ase_task_doc.mol_or_struct, f"meta_{k}": ase_task_doc.mol_or_struct})
259+
return getattr(meta_class, f"from_{k}")(**task_document_kwargs)
260+
158261
@property
159262
def forcefield_objects(self) -> Optional[dict[AseObject, Any]]:
160263
"""Alias `objects` attr for backwards compatibility."""

0 commit comments

Comments
 (0)