Skip to content

Commit b9c665e

Browse files
authored
Merge pull request #35 from QuantumChemist/merge_mlip_fitting_from_yb
[WIP] Merge mlip_fitting code from Yuanbin into autoplex
2 parents 9376607 + f127524 commit b9c665e

22 files changed

Lines changed: 2577 additions & 28 deletions

File tree

autoplex/auto/phonons/flows.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def make(
154154
add_data_ml_phonon = get_phonon_ml_calculation_jobs(
155155
structure=benchmark_structure,
156156
min_length=self.min_length,
157-
ml_dir=add_data_fit.output,
157+
ml_dir=add_data_fit.output["mlip_xml"],
158158
)
159159
flows.append(add_data_ml_phonon)
160160

@@ -407,7 +407,7 @@ def make(
407407
isolated_atoms_energy: list.
408408
Isolated atoms energy list
409409
fit_input: list.
410-
Mixed list of dictionary and lists
410+
Mixed list of dictionary and lists of the fit input data.
411411
xyz_file: str or None
412412
a possibly already existing xyz file
413413
fit_kwargs : dict.

autoplex/benchmark/phonons/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def compare_plot(
114114
plotter = PhononBSPlotter(bs=ml_bs)
115115
plotter2 = PhononBSPlotter(bs=dft_bs)
116116
new_plotter = plotter.plot_compare(plotter2)
117-
new_plotter.figure.savefig(file_name)
117+
118+
new_plotter.figure.savefig(fname=file_name)
118119

119120
return new_plotter.figure

autoplex/fitting/flows.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from pathlib import Path
1010
from jobflow import Flow, Maker
1111

12-
from autoplex.fitting.jobs import gapfit
12+
from autoplex.mlip_fitting.fitting.mlip_fitting import MLIPFitMaker as YbMLIPFitMaker
13+
from autoplex.mlip_fitting.fitting.mlip_fitting import data_preprocessing
1314

1415
__all__ = ["MLIPFitMaker"]
1516

@@ -57,12 +58,23 @@ def make(
5758
dict including gap fit keyword args.
5859
"""
5960
jobs = []
60-
gap_fit_job = gapfit(
61-
fit_input=fit_input,
62-
isolated_atoms=species_list,
63-
isolated_atoms_energy=iso_atom_energy,
64-
xyz_file=xyz_file,
65-
fit_kwargs=fit_kwargs,
61+
# gap_fit_job = gapfit(
62+
# fit_input=fit_input,
63+
# isolated_atoms=species_list,
64+
# isolated_atoms_energy=iso_atom_energy,
65+
# xyz_file=xyz_file,
66+
# fit_kwargs=fit_kwargs,
67+
# )
68+
data_prep_job = data_preprocessing(
69+
split_ratio=0.4, regularization=True, distillation=True, f_max=40
70+
).make(
71+
fit_input=fit_input, pre_database_dir=None, xyz_file=xyz_file
72+
) # CE: I think pre_database_dir and xyz_file
73+
# are meant to be the same
74+
jobs.append(data_prep_job)
75+
gap_fit_job = YbMLIPFitMaker(mlip_type="GAP").make(
76+
database_dir=data_prep_job.output,
77+
isol_es=None, # nequip={},
6678
)
6779
jobs.append(gap_fit_job) # type: ignore
6880

autoplex/fitting/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,4 @@ def outcar_2_extended_xyz(
177177
i.pbc = True
178178
if xyz_file is not None:
179179
shutil.copy2(xyz_file, os.getcwd())
180-
write("trainGAP.xyz", file, append=True)
180+
write("vasp_ref.extxyz", file, append=True)

autoplex/mlip_fitting/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
For using this code for fitting MLIPs, one could use the following two jobs:
2+
3+
4+
from autoplex.fitting.mlip_fitting import data_preprocessing, mlip_fit
5+
job1 = data_preprocessing(split_ratio=0.1, regularization=True, distillation=True, f_max=40).make(fit_input=fit_input, pre_database_dir=None)
6+
job2 = mlip_fit(mlip_type='GAP').make(database_dir=job1.output, isol_es=None)
7+
8+
The "split_ratio" parameter is used to divide the training set and the test set. A value of 0.1 means that the ratio of the training set to the test set is 9:1.
9+
10+
Here 'isol_es' is for the ACE fitting, because the ACE model prefers to use cohesive energy for fitting instead of free energy. The format of 'isol_es' for Si is like isol_es={14: -0.81432699}.
11+
12+
For GAP fitting, the isol_es will be automatically accepted in the "train.extxyz" as a configuration with the "config_type=isolated_atom".
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Some description goes here."""
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
"""The mlip_fitting part."""
2+
3+
from __future__ import annotations
4+
5+
import os
6+
import shutil
7+
from dataclasses import dataclass
8+
from pathlib import Path
9+
10+
import ase.io
11+
from jobflow import Maker, job
12+
13+
from autoplex.fitting.utils import get_list_of_vasp_calc_dirs, outcar_2_extended_xyz
14+
from autoplex.mlip_fitting.fitting.mlip_models import gap_fitting # , ace_fitting
15+
from autoplex.mlip_fitting.fitting.regularization import set_sigma
16+
from autoplex.mlip_fitting.fitting.utilities import data_distillation, split_dataset
17+
18+
19+
@dataclass
20+
class data_preprocessing(Maker):
21+
"""
22+
Data preprocessing function.
23+
24+
Parameters
25+
----------
26+
name : str
27+
Name of the flows produced by this maker.
28+
split_ratio: float
29+
Parameter to divide the training set and the test set.
30+
A value of 0.1 means that the ratio of the training set to the test set is 9:1
31+
regularization: bool
32+
For using regularization.
33+
distillation: bool
34+
For using distillation.
35+
f_max: float
36+
Maximally allowed force in the data set.
37+
38+
"""
39+
40+
name: str = "data_preprocessing_for_fitting"
41+
split_ratio: float = 0.5
42+
regularization: bool = False
43+
distillation: bool = False
44+
f_max: float = 40.0
45+
46+
@job
47+
def make(
48+
self,
49+
fit_input: dict,
50+
pre_database_dir: str,
51+
xyz_file: str | None = None,
52+
):
53+
"""
54+
Maker for data preprocessing.
55+
56+
Parameters
57+
----------
58+
fit_input:
59+
Mixed list of dictionary and lists of the fit input data.
60+
pre_database_dir:
61+
the pre-database directory.
62+
xyz_file:
63+
the already existing training datasets labeled by VASP.
64+
65+
"""
66+
config_types = []
67+
68+
list_of_vasp_calc_dirs = get_list_of_vasp_calc_dirs(flow_output=fit_input)
69+
70+
config_types = [
71+
key
72+
for key, value in fit_input.items()
73+
for key2, value2 in value.items()
74+
if key2 != "phonon_data"
75+
for _ in value2[0]
76+
]
77+
78+
outcar_2_extended_xyz(
79+
path_to_vasp_static_calcs=list_of_vasp_calc_dirs,
80+
config_types=config_types,
81+
xyz_file=xyz_file,
82+
)
83+
84+
# reject structures with large force components
85+
atoms = (
86+
data_distillation("vasp_ref.extxyz", self.f_max)
87+
if self.distillation
88+
else ase.io.read("vasp_ref.extxyz", index=":")
89+
)
90+
91+
# split dataset into training and testing datasets with a ratio of 9:1
92+
(train_structures, test_structures) = split_dataset(atoms, self.split_ratio)
93+
94+
# Merging database
95+
if pre_database_dir and os.path.exists(pre_database_dir):
96+
files_to_copy = ["train.extxyz", "test.extxyz"]
97+
current_working_directory = os.getcwd()
98+
99+
for file_name in files_to_copy:
100+
source_file_path = os.path.join(pre_database_dir, file_name)
101+
destination_file_path = os.path.join(
102+
current_working_directory, file_name
103+
)
104+
shutil.copy(source_file_path, destination_file_path)
105+
print(f"File {file_name} has been copied to {destination_file_path}")
106+
107+
ase.io.write("train.extxyz", train_structures, format="extxyz", append=True)
108+
ase.io.write("test.extxyz", test_structures, format="extxyz", append=True)
109+
110+
if self.regularization:
111+
atoms = ase.io.read("train.extxyz", index=":")
112+
atom_with_sigma = set_sigma(
113+
atoms,
114+
etup=[(0.1, 1), (0.001, 0.1), (0.0316, 0.316), (0.0632, 0.632)],
115+
)
116+
ase.io.write("train_with_sigma.extxyz", atom_with_sigma, format="extxyz")
117+
118+
# database_path = Path.cwd()
119+
120+
return Path.cwd() # database_path
121+
122+
123+
@dataclass
124+
class MLIPFitMaker(Maker):
125+
"""
126+
Maker to fitting potential.
127+
128+
Parameters
129+
----------
130+
name : str
131+
Name of the flows produced by this maker.
132+
mlip_type: str
133+
Choose one specific MLIP type:
134+
'GAP' | 'SNAP' | 'ACE' | 'Nequip' | 'Allegro' | 'MACE'
135+
HPO: bool
136+
call hyperparameter optimization (HPO) or not
137+
138+
"""
139+
140+
name: str = "MLIP_FIT"
141+
mlip_type: str | None = None
142+
HPO: bool = False
143+
144+
@job
145+
def make(
146+
self,
147+
database_dir: str,
148+
# nequip: dict,
149+
gap_para=None,
150+
# ace_para={
151+
# "energy_name": "REF_energy",
152+
# "force_name": "REF_forces",
153+
# "virial_name": "REF_virials",
154+
# "order": 3,
155+
# "totaldegree": 6,
156+
# "cutoff": 2.0,
157+
# "solver": "BLR",
158+
# },
159+
isol_es: None = None,
160+
num_of_threads: int = 128,
161+
**kwargs,
162+
):
163+
"""
164+
Maker for data preprocessing.
165+
166+
Parameters
167+
----------
168+
database_dir:
169+
the database directory.
170+
nequip:
171+
nequip parameters.
172+
gap_para: dict
173+
gap fit parameters.
174+
isol_es:
175+
isolated es.
176+
num_of_threads: int
177+
number of threads to be used.
178+
179+
"""
180+
if gap_para is None:
181+
gap_para = {"two_body": True, "three_body": False, "soap": True}
182+
183+
mlip_path = Path.cwd()
184+
if os.path.join(database_dir, "train_with_sigma.extxyz"):
185+
shutil.copy(
186+
os.path.join(database_dir, "train_with_sigma.extxyz"),
187+
os.path.join(mlip_path, "train_with_sigma.extxyz"),
188+
)
189+
shutil.copy(
190+
os.path.join(database_dir, "test.extxyz"),
191+
os.path.join(mlip_path, "test.extxyz"),
192+
)
193+
shutil.copy(
194+
os.path.join(database_dir, "train.extxyz"),
195+
os.path.join(mlip_path, "train.extxyz"),
196+
)
197+
198+
if self.mlip_type is None:
199+
raise ValueError(
200+
"MLIP type is not defined! "
201+
"The current version supports the fitting of GAP, SNAP, ACE, Nequip, Allegro, or MACE."
202+
)
203+
204+
if self.mlip_type == "GAP":
205+
train_error, test_error = gap_fitting(
206+
dir=database_dir,
207+
two_body=gap_para["two_body"],
208+
three_body=gap_para["three_body"],
209+
soap=["soap"],
210+
)
211+
212+
convergence = False
213+
if test_error < 0.01:
214+
convergence = True
215+
216+
return {
217+
"mlip_path": mlip_path,
218+
"mlip_xml": mlip_path.joinpath("gap_file.xml"),
219+
"train_error": train_error,
220+
"test_error": test_error,
221+
"convergence": convergence,
222+
}

0 commit comments

Comments
 (0)