Skip to content

Commit 57b2404

Browse files
authored
Merge pull request #74 from QuantumChemist/main
Fully Integrate all MLIPs
2 parents 1e5313d + 4da9dc3 commit 57b2404

File tree

12 files changed

+497
-146
lines changed

12 files changed

+497
-146
lines changed

autoplex/auto/phonons/flows.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ class CompleteDFTvsMLBenchmarkWorkflow(Maker):
150150
atomwise_regularization_list: list | None = None
151151
soap_delta_list: list | None = None
152152
n_sparse_list: list | None = None
153+
benchmark_kwargs: dict = field(default_factory=dict)
153154

154155
def make(
155156
self,
@@ -263,7 +264,7 @@ def make(
263264
mlip_hyper=ml_hyper,
264265
).make(
265266
species_list=isoatoms.output["species"],
266-
isolated_atoms_energy=isoatoms.output["energies"],
267+
isolated_atoms_energies=isoatoms.output["energies"],
267268
fit_input=fit_input,
268269
split_ratio=split_ratio,
269270
f_max=f_max,
@@ -311,6 +312,7 @@ def make(
311312
symprec=self.symprec,
312313
phonon_displacement_maker=self.phonon_displacement_maker,
313314
dft_references=dft_references,
315+
**self.benchmark_kwargs,
314316
)
315317
flows.append(complete_bm)
316318
bm_outputs.append(complete_bm.output)
@@ -343,7 +345,7 @@ def make(
343345
mlip_type=ml_model, mlip_hyper=ml_hyper
344346
).make(
345347
species_list=isoatoms.output["species"],
346-
isolated_atoms_energy=isoatoms.output["energies"],
348+
isolated_atoms_energies=isoatoms.output["energies"],
347349
fit_input=fit_input,
348350
split_ratio=split_ratio,
349351
f_max=f_max,
@@ -379,6 +381,7 @@ def make(
379381
symprec=self.symprec,
380382
phonon_displacement_maker=self.phonon_displacement_maker,
381383
dft_references=dft_references,
384+
**self.benchmark_kwargs,
382385
)
383386
flows.append(complete_bm)
384387
bm_outputs.append(complete_bm.output)

autoplex/auto/phonons/jobs.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def complete_benchmark( # this function was put here to prevent circular import
3636
symprec,
3737
phonon_displacement_maker: BaseVaspMaker,
3838
dft_references=None,
39+
relax_maker_kwargs: dict | None = None,
40+
static_maker_kwargs: dict | None = None,
41+
**ml_phonon_maker_kwargs,
3942
):
4043
"""
4144
Construct a complete flow for benchmarking the MLIP fit quality using a DFT based phonon structure.
@@ -76,6 +79,12 @@ def complete_benchmark( # this function was put here to prevent circular import
7679
Maker used to compute the forces for a supercell.
7780
dft_references:
7881
a list of DFT reference files containing the PhononBSDOCDoc object. Default None.
82+
relax_maker_kwargs: dict
83+
Keyword arguments that can be passed to the RelaxMaker.
84+
static_maker_kwargs: dict
85+
Keyword arguments that can be passed to the StaticMaker.
86+
ml_phonon_maker_kwargs: dict
87+
Keyword arguments that can be passed to the MLPhononMaker.
7988
"""
8089
jobs = []
8190
collect_output = []
@@ -84,16 +93,34 @@ def complete_benchmark( # this function was put here to prevent circular import
8493
if min_length >= 18:
8594
phonon_displacement_maker = TightDFTStaticMakerBigSupercells()
8695
for suffix in ["", "_wo_sigma", "_phonon", "_rand_struc"]:
87-
if Path(Path(ml_path) / f"gap_file{suffix}.xml").exists():
88-
# TODO: this needs to beextended for the other MLIPs
96+
# _wo_sigma", "_phonon", "_rand_struc" only available for GAP at the moment
97+
if ml_model == "GAP":
98+
ml_potential = Path(ml_path) / f"gap_file{suffix}.xml"
99+
elif ml_model == "J-ACE":
100+
raise UserWarning("No atomate2 ACE.jl PhononMaker implemented.")
101+
elif ml_model in ["M3GNET"]:
102+
ml_potential = Path(ml_path.join(suffix)) / "training"
103+
# M3GNet requires path
104+
# also need to find a different solution for separated fit then
105+
elif ml_model in ["NEQUIP"]:
106+
ml_potential = Path(ml_path) / f"deployed_nequip_model{suffix}.pth"
107+
else: # MACE
108+
ml_potential = Path(ml_path) / f"MACE_model{suffix}.model"
109+
110+
if Path(ml_potential).exists():
89111
add_data_ml_phonon = MLPhononMaker(
90112
min_length=min_length,
113+
relax_maker_kwargs=relax_maker_kwargs,
114+
static_maker_kwargs=static_maker_kwargs,
91115
).make_from_ml_model(
92116
structure=benchmark_structure,
93-
ml_model=ml_path,
94-
suffix=suffix,
117+
ml_model=ml_model,
118+
potential_file=ml_potential,
119+
**ml_phonon_maker_kwargs,
95120
)
96121
jobs.append(add_data_ml_phonon)
122+
123+
# DFT benchmark reference preparations
97124
if dft_references is None and benchmark_mp_ids is not None:
98125
if (
99126
benchmark_mp_ids[ibenchmark_structure] in mp_ids
@@ -322,18 +349,22 @@ def get_iso_atom(structure_list: list[Structure]):
322349
list of pymatgen Structure objects
323350
"""
324351
jobs = []
352+
iso_atoms_dict = {}
325353
all_species = list(
326354
{specie for s in structure_list for specie in s.types_of_species}
327355
)
328356

329357
isoatoms = IsoAtomMaker().make(all_species=all_species)
330358
jobs.append(isoatoms)
331359

360+
for i, species in enumerate(all_species):
361+
iso_atoms_dict.update({species.number: isoatoms.output["energies"][i]})
362+
332363
flow = Flow(
333364
jobs,
334365
{
335366
"species": all_species,
336-
"energies": isoatoms.output["energies"],
367+
"energies": iso_atoms_dict,
337368
"dirs": isoatoms.output["dirs"],
338369
},
339370
)

autoplex/data/phonons/flows.py

Lines changed: 114 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
ForceFieldStaticMaker,
1818
GAPRelaxMaker,
1919
GAPStaticMaker,
20+
M3GNetRelaxMaker,
21+
M3GNetStaticMaker,
22+
MACERelaxMaker,
23+
MACEStaticMaker,
24+
NequipRelaxMaker,
25+
NequipStaticMaker,
2026
)
2127
from atomate2.vasp.flows.core import DoubleRelaxMaker
2228
from atomate2.vasp.flows.phonons import PhononMaker
@@ -27,6 +33,7 @@
2733
from pymatgen.core import Molecule, Site
2834

2935
from autoplex.data.common.jobs import generate_randomized_structures
36+
from autoplex.data.phonons.utils import ml_phonon_maker_preparation
3037

3138
__all__ = [
3239
"DFTPhononMaker",
@@ -332,17 +339,17 @@ class MLPhononMaker(FFPhononMaker):
332339
High-throughput electronic band structure calculations:
333340
Challenges and tools. Computational Materials Science,
334341
49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010.
335-
We will however use seekpath and primitive structures
342+
We will, however, use seekpath and primitive structures
336343
as determined by from phonopy to compute the phonon band structure
337-
bulk_relax_maker : .ForceFieldRelaxMaker or None
344+
bulk_relax_maker: .ForceFieldRelaxMaker or None
338345
A maker to perform a tight relaxation on the bulk.
339346
Set to ``None`` to skip the
340347
bulk relaxation
341-
static_energy_maker : .ForceFieldStaticMaker or None
348+
static_energy_maker: .ForceFieldStaticMaker or None
342349
A maker to perform the computation of the DFT energy on the bulk.
343350
Set to ``None`` to skip the
344351
static energy computation
345-
phonon_displacement_maker : .ForceFieldStaticMaker or None
352+
phonon_displacement_maker: .ForceFieldStaticMaker or None
346353
Maker used to compute the forces for a supercell.
347354
generate_frequencies_eigenvectors_kwargs : dict
348355
Keyword arguments passed to :obj:`generate_frequencies_eigenvectors`.
@@ -364,6 +371,10 @@ class MLPhononMaker(FFPhononMaker):
364371
in the future
365372
store_force_constants: bool
366373
if True, force constants will be stored
374+
relax_maker_kwargs: dict
375+
Keyword arguments that can be passed to the RelaxMaker.
376+
static_maker_kwargs: dict
377+
Keyword arguments that can be passed to the StaticMaker.
367378
"""
368379

369380
name: str = "ml phonon"
@@ -374,7 +385,7 @@ class MLPhononMaker(FFPhononMaker):
374385
)
375386
)
376387
phonon_displacement_maker: ForceFieldStaticMaker | None = field(
377-
default_factory=lambda: GAPStaticMaker(name="ml phonon static")
388+
default_factory=lambda: GAPStaticMaker(name="gap phonon static")
378389
)
379390
static_energy_maker: ForceFieldStaticMaker | None = field(
380391
default_factory=lambda: GAPStaticMaker()
@@ -383,11 +394,18 @@ class MLPhononMaker(FFPhononMaker):
383394
generate_frequencies_eigenvectors_kwargs: dict = field(
384395
default_factory=lambda: {"units": "THz", "tol_imaginary_modes": 1e-1}
385396
)
386-
relax_maker_kwargs: dict = field(default_factory=dict)
387-
static_maker_kwargs: dict = field(default_factory=dict)
397+
relax_maker_kwargs: dict | None = field(default_factory=dict)
398+
static_maker_kwargs: dict | None = field(default_factory=dict)
388399

389400
@job
390-
def make_from_ml_model(self, structure, ml_model, suffix: str = "", **make_kwargs):
401+
def make_from_ml_model(
402+
self,
403+
structure,
404+
potential_file,
405+
ml_model: str = "GAP",
406+
calculator_kwargs: dict | None = None,
407+
**make_kwargs,
408+
):
391409
"""
392410
Maker for GAP phonon jobs.
393411
@@ -397,10 +415,13 @@ def make_from_ml_model(self, structure, ml_model, suffix: str = "", **make_kwarg
397415
A pymatgen structure. Please start with a structure
398416
that is nearly fully optimized as the internal optimizers
399417
have very strict settings!
400-
ml_model : str
401-
Complete path to MLIP file(s).
402-
suffix:
403-
Train, test and MLIP suffix ("", "_wo_sigma", "_phonon", "_rand_struc").
418+
ml_model: str
419+
ML model to be used. Default is GAP.
420+
potential_file :
421+
Complete path to MLIP file(s)
422+
Train, test and MLIP files (+ suffixes "", "_wo_sigma", "_phonon", "_rand_struc").
423+
calculator_kwargs :
424+
Keyword arguments for the ASE Calculator.
404425
make_kwargs :
405426
Keyword arguments for the PhononMaker.
406427
@@ -409,41 +430,93 @@ def make_from_ml_model(self, structure, ml_model, suffix: str = "", **make_kwarg
409430
PhononMaker jobs.
410431
411432
"""
412-
ml_model = ml_model + f"/gap_file{suffix}.xml"
413-
if self.bulk_relax_maker is not None:
414-
br = self.bulk_relax_maker
415-
self.bulk_relax_maker = br.update_kwargs(
416-
update={
417-
"calculator_kwargs": {
418-
"args_str": "IP GAP",
419-
"param_filename": str(ml_model),
420-
},
421-
**self.relax_maker_kwargs,
433+
if ml_model == "GAP":
434+
if calculator_kwargs is None:
435+
calculator_kwargs = {
436+
"args_str": "IP GAP",
437+
"param_filename": str(potential_file),
422438
}
439+
440+
ml_prep = ml_phonon_maker_preparation(
441+
bulk_relax_maker=self.bulk_relax_maker,
442+
phonon_displacement_maker=self.phonon_displacement_maker,
443+
static_energy_maker=self.static_energy_maker,
444+
calculator_kwargs=calculator_kwargs,
445+
relax_maker_kwargs=self.relax_maker_kwargs,
446+
static_maker_kwargs=self.static_maker_kwargs,
423447
)
424-
if self.phonon_displacement_maker is not None:
425-
ph_disp = self.phonon_displacement_maker
426-
self.phonon_displacement_maker = ph_disp.update_kwargs(
427-
update={
428-
"calculator_kwargs": {
429-
"args_str": "IP GAP",
430-
"param_filename": str(ml_model),
431-
},
432-
**self.static_maker_kwargs,
448+
449+
elif ml_model == "J-ACE":
450+
raise UserWarning("No atomate2 ACE.jl PhononMaker implemented.")
451+
452+
elif ml_model == "NEQUIP":
453+
if calculator_kwargs is None:
454+
calculator_kwargs = {
455+
"model_path": str(potential_file),
456+
"device": "cuda",
433457
}
458+
else:
459+
calculator_kwargs.update({"model_path": str(potential_file)})
460+
461+
ml_prep = ml_phonon_maker_preparation(
462+
bulk_relax_maker=NequipRelaxMaker(
463+
relax_cell=True, relax_kwargs={"interval": 500}
464+
),
465+
phonon_displacement_maker=NequipStaticMaker(
466+
name="nequip phonon static"
467+
),
468+
static_energy_maker=NequipStaticMaker(),
469+
calculator_kwargs=calculator_kwargs,
470+
relax_maker_kwargs=self.relax_maker_kwargs,
471+
static_maker_kwargs=self.static_maker_kwargs,
434472
)
435-
if self.static_energy_maker is not None:
436-
stat_en = self.static_energy_maker
437-
self.static_energy_maker = stat_en.update_kwargs(
438-
update={
439-
"calculator_kwargs": {
440-
"args_str": "IP GAP",
441-
"param_filename": str(ml_model),
442-
},
443-
**self.static_maker_kwargs,
444-
}
473+
474+
elif ml_model == "M3GNET":
475+
if calculator_kwargs is None:
476+
calculator_kwargs = {"path": str(potential_file)}
477+
478+
ml_prep = ml_phonon_maker_preparation(
479+
bulk_relax_maker=M3GNetRelaxMaker(
480+
relax_cell=True, relax_kwargs={"interval": 500}
481+
),
482+
phonon_displacement_maker=M3GNetStaticMaker(
483+
name="m3gnet phonon static"
484+
),
485+
static_energy_maker=M3GNetStaticMaker(),
486+
calculator_kwargs=calculator_kwargs,
487+
relax_maker_kwargs=self.relax_maker_kwargs,
488+
static_maker_kwargs=self.static_maker_kwargs,
445489
)
446490

491+
else: # MACE
492+
if calculator_kwargs is None:
493+
calculator_kwargs = {"model": str(potential_file), "device": "cuda"}
494+
elif "model" in calculator_kwargs:
495+
calculator_kwargs.update(
496+
{"default_dtype": "float64"}
497+
) # Use float64 for geometry optimization.
498+
else:
499+
calculator_kwargs.update(
500+
{"model": str(potential_file), "default_dtype": "float64"}
501+
)
502+
503+
ml_prep = ml_phonon_maker_preparation(
504+
bulk_relax_maker=MACERelaxMaker(
505+
relax_cell=True, relax_kwargs={"interval": 500}
506+
),
507+
phonon_displacement_maker=MACEStaticMaker(name="mace phonon static"),
508+
static_energy_maker=MACEStaticMaker(),
509+
calculator_kwargs=calculator_kwargs,
510+
relax_maker_kwargs=self.relax_maker_kwargs,
511+
static_maker_kwargs=self.static_maker_kwargs,
512+
)
513+
514+
(
515+
self.bulk_relax_maker,
516+
self.phonon_displacement_maker,
517+
self.static_energy_maker,
518+
) = ml_prep
519+
447520
flow = self.make(structure=structure, **make_kwargs)
448521
return Response(replace=flow, output=flow.output)
449522

0 commit comments

Comments
 (0)