Skip to content

Commit 7027247

Browse files
authored
forcefields: allow specifying custom calculator in forcefield makers (#1362)
* Allow ext-load calculator in forcefield makers * Fix header depth * Add docs for force_field_name * Infer calculator_meta from ase_calculator_name
1 parent f85e91e commit 7027247

File tree

21 files changed

+312
-47
lines changed

21 files changed

+312
-47
lines changed

docs/user/codes/forcefields.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
# Machine Learning forcefields / interatomic potentials
44

55
`atomate2` includes an interface to a few common machine learning interatomic potentials (MLIPs), also known variously as machine learning forcefields (MLFFs), or foundation potentials (FPs) for universal variants.
6+
7+
Most of `Maker` classes using the forcefields inherit from `atomate2.forcefields.utils.ForceFieldMixin` to specify which forcefield to use.
8+
The `ForceFieldMixin` mixin provides the following configurable parameters:
9+
10+
- `force_field_name`: Name of the forcefield to use.
11+
- `calculator_kwargs`: Keyword arguments to pass to the corresponding ASE calculator.
12+
13+
These parameters are passed to `atomate2.forcefields.utils.ase_calculator()` to instantiate the appropriate ASE calculator.
14+
15+
The `force_field_name` should be either one of predefined `atomate2.forcefields.utils.MLFF` (or its string equivalent) or a dictionary decodable as a class or function for ASE calculator as follows.
16+
17+
## Using predefined forcefields supported via `atomate2.forcefields.utils.MLFF`
18+
619
Support is provided for the following models, which can be selected using `atomate2.forcefields.utils.MLFF`, as shown in the table below.
720
**You need only install packages for the forcefields you wish to use.**
821

@@ -20,3 +33,18 @@ Support is provided for the following models, which can be selected using `atoma
2033
| Neuroevolution Potential (NEP) | `NEP` | [10.1103/PhysRevB.104.104309](https://doi.org/10.1103/PhysRevB.104.104309) | Relies on `calorine` package |
2134
| Neural Equivariant Interatomic Potentials (Nequip) | `Nequip` | [10.1038/s41467-022-29939-5](https://doi.org/10.1038/s41467-022-29939-5) | Relies on the `nequip` package |
2235
| SevenNet | `SevenNet` | [10.1021/acs.jctc.4c00190](https://doi.org/10.1021/acs.jctc.4c00190) | Relies on the `sevenn` package |
36+
37+
## Using custom forcefields by dictionary
38+
39+
`force_field_name` also accepts a MSONable dictionary for specifying a custom ASE calculator class or function [^calculator-meta-type-annotation].
40+
For example, a `Job` created with the following code snippet instantiates `chgnet.model.dynamics.CHGNetCalculator` as the ASE calculator:
41+
```python
42+
job = ForceFieldStaticMaker(
43+
force_field_name={
44+
"@module": "chgnet.model.dynamics",
45+
"@callable": "CHGNetCalculator",
46+
}
47+
).make(structure)
48+
```
49+
50+
[^calculator-meta-type-annotation]: In this context, the type annotation of the decoded dict should be either `Type[Calculator]` or `Callable[..., Calculator]`, where `Calculator` is from `ase.calculators.calculator`.

docs/user/key_concepts_overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ An `InputSet` is a convenient way to provide a collection of input data for one
103103
The [pymatgen](https://github.com/materialsproject/pymatgen) class `InputSet` is a core class to manage and write the input files for the several computational codes to a file location the user specifies.
104104
There are predefined "recipes" for generating `InputSets` tailored to specific tasks like structural relaxation or the band structure calculation and more, that are provided as `InputGenerator` classes.
105105

106-
## Technical Aspects
106+
### Technical Aspects
107107

108108
The `InputSet` objects posses the `write_input()` method that is used to write all the necessary files.
109109

src/atomate2/forcefields/flows/approx_neb.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,19 @@ def get_charge_density(
6666
@classmethod
6767
def from_force_field_name(
6868
cls,
69-
force_field_name: str | MLFF,
69+
force_field_name: str | MLFF | dict,
7070
**kwargs,
7171
) -> Self:
7272
"""
7373
Create an ApproxNEB flow from a forcefield name.
7474
7575
Parameters
7676
----------
77-
force_field_name : str or .MLFF
77+
force_field_name : str or .MLFF or dict
7878
The name of the force field.
7979
**kwargs
8080
Additional kwargs to pass to ApproxNEB
8181
82-
8382
Returns
8483
-------
8584
MLFFApproxNebFromEndpointsMaker

src/atomate2/forcefields/flows/elastic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def prev_calc_dir_argname(self) -> str | None:
103103
@classmethod
104104
def from_force_field_name(
105105
cls,
106-
force_field_name: str | MLFF,
106+
force_field_name: str | MLFF | dict,
107107
mlff_kwargs: dict | None = None,
108108
**kwargs,
109109
) -> Self:
@@ -112,7 +112,7 @@ def from_force_field_name(
112112
113113
Parameters
114114
----------
115-
force_field_name : str or .MLFF
115+
force_field_name : str or .MLFF or dict
116116
The name of the force field.
117117
mlff_kwargs : dict or None (default)
118118
kwargs to pass to `ForceFieldRelaxMaker`.

src/atomate2/forcefields/flows/eos.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class ForceFieldEosMaker(CommonEosMaker):
5959
@classmethod
6060
def from_force_field_name(
6161
cls,
62-
force_field_name: str | MLFF,
62+
force_field_name: str | MLFF | dict,
6363
relax_initial_structure: bool = True,
6464
**kwargs,
6565
) -> Self:
@@ -68,7 +68,7 @@ def from_force_field_name(
6868
6969
Parameters
7070
----------
71-
force_field_name : str or .MLFF
71+
force_field_name : str or .MLFF or dict
7272
The name of the force field.
7373
relax_initial_structure: bool = True
7474
Whether to relax the initial structure before performing an EOS fit.

src/atomate2/forcefields/flows/phonons.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,15 @@ def mlff(self) -> MLFF:
158158
"""The MLFF enum corresponding to the force field name."""
159159
return self.phonon_displacement_maker.mlff
160160

161+
@property
162+
def ase_calculator_name(self) -> str:
163+
"""The name of the ASE calculator used in this flow."""
164+
return self.phonon_displacement_maker.ase_calculator_name
165+
161166
@classmethod
162167
def from_force_field_name(
163168
cls,
164-
force_field_name: str | MLFF,
169+
force_field_name: str | MLFF | dict,
165170
relax_initial_structure: bool = True,
166171
**kwargs,
167172
) -> Self:
@@ -170,14 +175,13 @@ def from_force_field_name(
170175
171176
Parameters
172177
----------
173-
force_field_name : str or .MLFF
178+
force_field_name : str or .MLFF or dict
174179
The name of the force field.
175180
relax_initial_structure: bool = True
176181
Whether to relax the initial structure before performing an EOS fit.
177182
**kwargs
178183
Additional kwargs to pass to PhononMaker
179184
180-
181185
Returns
182186
-------
183187
PhononMaker

src/atomate2/forcefields/flows/qha.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def prev_calc_dir_argname(self) -> None:
9292
@classmethod
9393
def from_force_field_name(
9494
cls,
95-
force_field_name: str | MLFF,
95+
force_field_name: str | MLFF | dict,
9696
relax_initial_structure: bool = True,
9797
run_eos_flow: bool = True,
9898
**kwargs,
@@ -102,7 +102,7 @@ def from_force_field_name(
102102
103103
Parameters
104104
----------
105-
force_field_name : str or .MLFF
105+
force_field_name : str or .MLFF or dict
106106
The name of the force field.
107107
relax_initial_structure: bool = True
108108
Whether to relax the initial structure before performing an EOS fit.
@@ -111,7 +111,6 @@ def from_force_field_name(
111111
**kwargs
112112
Additional kwargs to pass to ForceFieldEosMaker
113113
114-
115114
Returns
116115
-------
117116
ForceFieldQhaMaker

src/atomate2/forcefields/jobs.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class ForceFieldRelaxMaker(ForceFieldMixin, AseRelaxMaker):
7373
----------
7474
name : str
7575
The job name.
76-
force_field_name : str or .MLFF
76+
force_field_name : str or .MLFF or dict
7777
The name of the force field.
7878
relax_cell : bool = True
7979
Whether to allow the cell shape/volume to change during relaxation.
@@ -106,7 +106,7 @@ class ForceFieldRelaxMaker(ForceFieldMixin, AseRelaxMaker):
106106
"""
107107

108108
name: str = "Force field relax"
109-
force_field_name: str | MLFF = MLFF.Forcefield
109+
force_field_name: str | MLFF | dict = MLFF.Forcefield
110110
relax_cell: bool = True
111111
fix_symmetry: bool = False
112112
symprec: float | None = 1e-2
@@ -142,9 +142,10 @@ def make(
142142
)
143143

144144
return ForceFieldTaskDocument.from_ase_compatible_result(
145-
str(self.force_field_name), # make mypy happy
145+
self.ase_calculator_name,
146146
ase_result,
147147
self.steps,
148+
calculator_meta=self.calculator_meta,
148149
relax_kwargs=self.relax_kwargs,
149150
optimizer_kwargs=self.optimizer_kwargs,
150151
relax_cell=self.relax_cell,
@@ -170,7 +171,7 @@ class ForceFieldStaticMaker(ForceFieldRelaxMaker):
170171
----------
171172
name : str
172173
The job name.
173-
force_field_name : str or .MLFF
174+
force_field_name : str or .MLFF or dict
174175
The name of the force field.
175176
calculator_kwargs : dict
176177
Keyword arguments that will get passed to the ASE calculator.
@@ -180,7 +181,7 @@ class ForceFieldStaticMaker(ForceFieldRelaxMaker):
180181
"""
181182

182183
name: str = "Force field static"
183-
force_field_name: str | MLFF = MLFF.Forcefield
184+
force_field_name: str | MLFF | dict = MLFF.Forcefield
184185
relax_cell: bool = False
185186
steps: int = 1
186187
relax_kwargs: dict = field(default_factory=dict)

src/atomate2/forcefields/md.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class ForceFieldMDMaker(ForceFieldMixin, AseMDMaker):
4343
----------
4444
name : str
4545
The name of the MD Maker
46-
force_field_name : str or .MLFF
46+
force_field_name : str or .MLFF or dict
4747
The name of the forcefield (for provenance)
4848
time_step : float | None = None.
4949
The timestep of the MD run in fs.
@@ -135,10 +135,11 @@ def make(
135135
)
136136

137137
return ForceFieldTaskDocument.from_ase_compatible_result(
138-
str(self.force_field_name), # make mypy happy
138+
self.ase_calculator_name,
139139
md_result,
140140
relax_cell=(self.ensemble == MDEnsemble.npt),
141141
steps=self.n_steps,
142+
calculator_meta=self.calculator_meta,
142143
relax_kwargs=None,
143144
optimizer_kwargs=None,
144145
fix_symmetry=False,

src/atomate2/forcefields/neb.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class ForceFieldNebFromImagesMaker(ForceFieldMixin, AseNebFromImagesMaker):
2424
"""Run NEB with an ML forcefield using ASE."""
2525

2626
name: str = "Forcefield NEB from images"
27-
force_field_name: str | MLFF = MLFF.Forcefield
27+
force_field_name: str | MLFF | dict = MLFF.Forcefield
2828

2929
@job(data=_FORCEFIELD_DATA_OBJECTS, schema=NebResult)
3030
def make(
@@ -49,7 +49,7 @@ class ForceFieldNebFromEndpointsMaker(ForceFieldMixin, AseNebFromEndpointsMaker)
4949
"""Run NEB with an ML forcefield using ASE."""
5050

5151
name: str = "Forcefield NEB from endpoints"
52-
force_field_name: str | MLFF = MLFF.Forcefield
52+
force_field_name: str | MLFF | dict = MLFF.Forcefield
5353

5454
@job(data=_FORCEFIELD_DATA_OBJECTS, schema=NebResult)
5555
def make(
@@ -69,19 +69,21 @@ def make(
6969
return self._run_ase_safe(images=images, prev_dir=prev_dir)
7070

7171
@classmethod
72-
def from_force_field_name(cls, force_field_name: str | MLFF, **kwargs) -> Self:
72+
def from_force_field_name(
73+
cls, force_field_name: str | MLFF | dict, **kwargs
74+
) -> Self:
7375
"""Create a force field NEB job from its name.
7476
7577
Parameters
7678
----------
77-
force_field_name : str or MLFF
78-
The name of the forcefield. Should be a valid MLFF member.
79+
force_field_name : str or MLFF or dict
80+
The name of the forcefield.
7981
**kwargs
8082
kwargs to pass to ForceFieldNebFromEndpointsMaker.
8183
"""
8284
endpoint_relax_maker = ForceFieldRelaxMaker(force_field_name=force_field_name)
8385
return cls(
84-
name=f"{force_field_name} NEB from endpoints maker",
86+
name=f"{endpoint_relax_maker.mlff.name} NEB from endpoints maker",
8587
endpoint_relax_maker=endpoint_relax_maker,
8688
force_field_name=force_field_name,
8789
**kwargs,

0 commit comments

Comments
 (0)