3
3
from __future__ import annotations
4
4
5
5
import logging
6
+ import time
6
7
from abc import ABCMeta , abstractmethod
7
8
from dataclasses import dataclass , field
8
9
from typing import TYPE_CHECKING
9
10
10
11
from ase .io import Trajectory as AseTrajectory
11
12
from emmet .core .vasp .calculation import StoreTrajectoryOption
12
13
from jobflow import Maker , job
14
+ from pymatgen .core import Molecule , Structure
13
15
from pymatgen .core .trajectory import Trajectory as PmgTrajectory
16
+ from pymatgen .io .ase import AseAtomsAdaptor
14
17
15
18
from atomate2 .ase .schemas import AseResult , AseTaskDoc
16
19
from atomate2 .ase .utils import AseRelaxer
21
24
from pathlib import Path
22
25
23
26
from ase .calculators .calculator import Calculator
24
- from pymatgen .core import Molecule , Structure
25
27
26
28
from atomate2 .ase .schemas import AseMoleculeTaskDoc , AseStructureTaskDoc
27
29
@@ -33,11 +35,33 @@ class AseMaker(Maker, metaclass=ABCMeta):
33
35
"""
34
36
Define basic template of ASE-based jobs.
35
37
36
- This class defines two functions relevant attributes
37
- for the ASE TaskDoc schemas, as well as two methods
38
- that must be implemented in subclasses:
39
- 1. `calculator`: the ASE .Calculator object
40
- 2. `run_ase`: which actually makes the call to ASE.
38
+ This class defines relevant attributes for the ASE TaskDoc
39
+ schemas, and one method that must be implemented in subclasses:
40
+ `calculator`: the ASE .Calculator object
41
+
42
+ The intent of this class is twofold: if users wish to have a
43
+ high-throughput way to access a calculator, they need only
44
+ subclass this class with a calculator defined, e.g., the following
45
+ is sufficient to define an EMT static calculator with basic I/O:
46
+
47
+ ```python
48
+ from ase.calculators.emt import EMT
49
+
50
+
51
+ @dataclass
52
+ class EMTStaticMaker(AseMaker):
53
+ name: str = "EMT static maker"
54
+
55
+ @property
56
+ def calculator(self):
57
+ return EMT()
58
+ ```
59
+
60
+ Note that the user should adapt `run_ase`, which is not a job
61
+ and makes a call to ASE, and `make`, which is a job, to their uses.
62
+
63
+ `run_ase` should return an `AseResult` which has basic calculation info.
64
+ `make` should return a pydantic-based document model with more details.
41
65
42
66
Parameters
43
67
----------
@@ -69,17 +93,35 @@ class AseMaker(Maker, metaclass=ABCMeta):
69
93
store_trajectory : StoreTrajectoryOption = StoreTrajectoryOption .NO
70
94
tags : list [str ] | None = None
71
95
72
- @abstractmethod
96
+ @job (data = _ASE_DATA_OBJECTS )
97
+ def make (
98
+ self ,
99
+ mol_or_struct : Molecule | Structure ,
100
+ prev_dir : str | Path | None = None ,
101
+ ) -> AseStructureTaskDoc | AseMoleculeTaskDoc :
102
+ """
103
+ Run ASE as job, can be re-implemented in subclasses.
104
+
105
+ Parameters
106
+ ----------
107
+ mol_or_struct: .Molecule or .Structure
108
+ pymatgen molecule or structure
109
+ prev_dir : str or Path or None
110
+ A previous calculation directory to copy output files from. Unused, just
111
+ added to match the method signature of other makers.
112
+ """
113
+ return AseTaskDoc .to_mol_or_struct_metadata_doc (
114
+ getattr (self .calculator , "name" , type (self .calculator ).__name__ ),
115
+ self .run_ase (mol_or_struct , prev_dir = prev_dir ),
116
+ )
117
+
73
118
def run_ase (
74
119
self ,
75
120
mol_or_struct : Structure | Molecule ,
76
121
prev_dir : str | Path | None = None ,
77
122
) -> AseResult :
78
123
"""
79
- Run ASE, method to be implemented in subclasses.
80
-
81
- This method exists to permit subclasses to redefine `make`
82
- for different output schemas.
124
+ Run ASE, can be re-implemented in subclasses.
83
125
84
126
Parameters
85
127
----------
@@ -89,7 +131,20 @@ def run_ase(
89
131
A previous calculation directory to copy output files from. Unused, just
90
132
added to match the method signature of other makers.
91
133
"""
92
- raise NotImplementedError
134
+ is_mol = isinstance (mol_or_struct , Molecule )
135
+ adaptor = AseAtomsAdaptor ()
136
+ atoms = adaptor .get_atoms (mol_or_struct )
137
+ atoms .calc = self .calculator
138
+ t_i = time .perf_counter ()
139
+ final_energy = atoms .get_potential_energy ()
140
+ t_f = time .perf_counter ()
141
+ return AseResult (
142
+ final_mol_or_struct = getattr (
143
+ adaptor , f"get_{ 'molecule' if is_mol else 'structure' } "
144
+ )(atoms ),
145
+ final_energy = final_energy ,
146
+ elapsed_time = t_f - t_i ,
147
+ )
93
148
94
149
@property
95
150
@abstractmethod
0 commit comments