Skip to content

Commit 9328a2b

Browse files
vir-k01BryantLi-BLIesoteric-ephemera
authored
LAMMPS Flows (#1185)
* New branch to add in atomate2 flows for lammps. Added basic file structure, copied over all the work done on atomate2-lammps (by @ml-evs and @gbrunin at Matgenix). The basic functions have been implemented, the actual tasks to be done are generating the right input sets for a wide range of lammps calculations that can be done, and to write a task doc that can handle the outputs of these calculations. Will update the run.py once I get it to work with a complied version of lammps for a simple test case. * Moved lammps invokation settings into common atomate2 settings object, run_lammps can now be executed * Removed unneccessary MPI settings from atomate2.SETTINGS. MPI options are probably better expressed in the LAMMPS_CMD, which is specified through the environment's atomate2.yaml file. * redid taskdoc to get log files read. Have to pass dump file reading to MDA via a builder * Fix input file name, modify template to account for more simulation parameters * Added helper function to convert dump to PMGTraj * Redid force_field setup, added correct _DATA_OBJECTS for jobstore, better output processing into the taskdoc * More expressive taskdoc, added ability to parse dump file into pmgtraj * Defined a BASE_SETTINGS object, base set now inherits lammps generators from pmg, better handling of inputs to the makers. TODO: make init more readable, and allow for better management of how upstream generators call base set generator * Redid trajectory reading into taskdoc with a new DumpConvertor class, based on atomate2.ase.utils.TrajectoryObserver. Also accounted for reading in molecules and saving as a trajectory. * Fixed issue with filename to write out trajectory to disk when building taskdoc * Streamlined lammps input set settings, moved default settings and keys to json files for easier access, added utility funcs to process settings dicts * Added basic implementation for NVT and NPT sets * Added ability to restart lammps run from prev_dir; added keys in settings to allow restart keyword to be provided in template * Fixed nvt and npt set initialization, accounted only for nose-hoover and langevin/berendsen for now. Added nph as a thermostat too. * Only add raw_log_file and trajectory with metadata to jobstore * Better management of settings keys in utils, account for random seed for langevin, need for nve integrator for nvt/npt with non-nose-hoover * Fixed fallback data types for raw_log and thermo_log if files arent parsed * Remove zipping dir for debugging purposes * Relaxed force field input to through warning if not dict, redid logic in update_settings function * Fix species variables in base set * Redid logic for update_settings * Added reudimentary sets for a custom lammps job based on a user provided template and for minimization calcs * Added task state to TaskDoc, made task state and store traj option to take in TaskState and StoreTrajectoryOption objects from emmet for consistency * Typo in StoreTrajectoryOption, added ValError exception when log file isn't complete * Fixed imports * Fixed super init for base set from core sets, fixed update settings func, indent * Better missing log file handling * Add time delay to task doc reading (debug) * Account for "ERROR" tag being anywhere in log file, actual error in last two lines * Missing subprocess.wait() added, removed time.sleep debugger * More info in lammps error message * Add in check if force_field is given as a string to ensure species is not none * bug in reading error from log * Better defaults for friction factors, fixed bug in update_settings * Bug in update_settings * Problem with passing settings from core to base sets * Another attempt at fixing passing kwargs from core to base sets * Added option for boundary (periodic or otherwise) and dimensions of simulation * Made structure optional to make since a lammpsdata obj can also be provided as a write_input_set_kwarg * Added neighbour list recalc line, needed to stabilize real simulations * Cleaned up init logic a bit, kwargs now consistent with AseMDMaker, fixed bug of pop instead of get from force_field dict, add comment for kwarg explanations * Massive cleanup of core sets and makers, settings now internally consistent across sets * lintig * Error with not having temperature and pressure as props of set, account for not storing trajectory in task_doc * By default write trajectory parsed from dump as "trajectory{id}.traj" * Option to now give force_field as input to either the input_set_generator or the maker directly, better convinience for the user imo. Moved force_field setting code to a function for reusability. Type fixes. * Add ability to take in openff interchange as input for FF and topology (to be tested) * corrected logic when providing interchange as FF * New LammpsInterchange class to allow for serialization of Interchange, moved Interchange definition to utils, default value for force_field now needs a pair_style if an interchage is used as input (using lj/cut/coul/cut 10 as default pair_stlye for now) * Updated minimization template * Added warnings when user asks to store trajectory in jobstore, now defaults to not parsing dumps due to possilbe size * Added more settings keys to templates, made file names consistent between templates * Moved update_settings to be a part of BaseLammpsSet. More intuitive file structure, MDEnsemble typing for ensemble, update_settings is now called for updating both settings dict and input set properties * Removed print statement * Remove all extra attributes from BaseLammpsSet, use settings dict for attributes instead * Now also parse dump files as text and add to taskdoc, might be easier than converting to a trajectory object but still having access to the simulation output * Now also take in a LammpsInputFile object as a template, super helpful when making an input file using the pmg io functions * First iteration of adding tests for lammps flows. * Move the dealing with template type logic to pymatgen * Cleaned up tests and paths, removed some redundancies * Add warning if store trajectory is specified by user, gzip output dir on finishing job * Use io funcs from pmg to read in job's input files, changed trajectory to trajectories in task_doc * Remove unnecessary lines about len(trajecotry) from job tests, added test minimization set, test for reading task_doc * Linting and cleaned up doc strings * Save parsed dump files to jobstore instead of taskdoc * typing; minor changes to dump convertor class (now takes in index to ensure only a single frame can be read), final structure of lammps run is parsed regardless of storetrajectoryoption, helps with consistency in outputs and for chaining together jobs * removed uncessary imports, fixed test with partial trajecotry * Added flow for melt+quench+thermalize (standard for amorphous/glassy material generation), added tests for flows * MASSIVE UPDATE 1. Made a lot of changes to how settings are processed in the input sets. Moved to a pmg style inputsetgenerator instead of wrapping around the very limiting generator in pmg (hope to move this into pmg in a future commit) 2. All the important settings that the user typically specifies are in an object called LammpsInputSettings. User shouldnt have to interact with this ever as the inputsetgenerator applies whatever updates are needed through a class method. 3. Force fields are better treated now: user can now give the FF as a file of coefficients (or in the dict format from the previous version), and this file is written into the lammpsrun through an "include <file>" command (even if the user gives the coeffs directly). FF styles have to be specified beforehand in the maker (will put up a tutorial clarifying the formats soon). 4. bug fixes in taskdoc and basemaker 5. updated tests to reflect the new input set format * Updated paths for tests * Added CustomLammpsMaker, cleaned up comments * Remove unneccesary settigns_files * Added docstrings, cleaned up old util functions, fixed imports * Make customlammpsmaker more flexible, add kspace settings to input Roll back customlammpsmaker to earlier iteration, now updates to the provided template can be done using a $variable style * Allow all types of inputs to customslammpsmaker to provide settings updates * Move inputsetgenerator to pmg Moved the BaseLammpsInputGenerator to pymatgen.io.lammps to be consistent with how other input sets are placed * Modified jobs and flows to now be consistent with LammpsSettings obj in pmg, modified tests accordingly. * better FF handling * Better FF handling * Synced sets and makers with Templated approached based on BaseLammpsSetGenerator in pmg * Added a tutorial notebook to show how to initialize and use the Lammps workflows * Updated tutorial * Updated notebook * Updated tests Updated test files (not the actual tests) to be consistent with new pmg input sets * Fixed typo in lammpsjob decorator; fixed logic in taskdoc * Fixed typo in lammpsjob decorator; fixed logic in taskdoc * Removed unecessary files Removed all instances of the original LammpsInterchange (too difficult to configure correctly), and removed the templates since they've been moved to pymatgen * remove comment * fix bug of metadata going to jobstore * linting * Added maker for NVE jobs * Consolidate lammps run commands * fix lammps mpi invokation * New branch to add in atomate2 flows for lammps. Added basic file structure, copied over all the work done on atomate2-lammps (by @ml-evs and @gbrunin at Matgenix). The basic functions have been implemented, the actual tasks to be done are generating the right input sets for a wide range of lammps calculations that can be done, and to write a task doc that can handle the outputs of these calculations. Will update the run.py once I get it to work with a complied version of lammps for a simple test case. * Moved lammps invokation settings into common atomate2 settings object, run_lammps can now be executed * Removed unneccessary MPI settings from atomate2.SETTINGS. MPI options are probably better expressed in the LAMMPS_CMD, which is specified through the environment's atomate2.yaml file. * redid taskdoc to get log files read. Have to pass dump file reading to MDA via a builder * Fix input file name, modify template to account for more simulation parameters * Added helper function to convert dump to PMGTraj * Redid force_field setup, added correct _DATA_OBJECTS for jobstore, better output processing into the taskdoc * More expressive taskdoc, added ability to parse dump file into pmgtraj * Defined a BASE_SETTINGS object, base set now inherits lammps generators from pmg, better handling of inputs to the makers. TODO: make init more readable, and allow for better management of how upstream generators call base set generator * Redid trajectory reading into taskdoc with a new DumpConvertor class, based on atomate2.ase.utils.TrajectoryObserver. Also accounted for reading in molecules and saving as a trajectory. * Fixed issue with filename to write out trajectory to disk when building taskdoc * Streamlined lammps input set settings, moved default settings and keys to json files for easier access, added utility funcs to process settings dicts * Added basic implementation for NVT and NPT sets * Added ability to restart lammps run from prev_dir; added keys in settings to allow restart keyword to be provided in template * Fixed nvt and npt set initialization, accounted only for nose-hoover and langevin/berendsen for now. Added nph as a thermostat too. * Only add raw_log_file and trajectory with metadata to jobstore * Better management of settings keys in utils, account for random seed for langevin, need for nve integrator for nvt/npt with non-nose-hoover * Fixed fallback data types for raw_log and thermo_log if files arent parsed * Remove zipping dir for debugging purposes * Relaxed force field input to through warning if not dict, redid logic in update_settings function * Fix species variables in base set * Redid logic for update_settings * Added reudimentary sets for a custom lammps job based on a user provided template and for minimization calcs * Added task state to TaskDoc, made task state and store traj option to take in TaskState and StoreTrajectoryOption objects from emmet for consistency * Typo in StoreTrajectoryOption, added ValError exception when log file isn't complete * Fixed imports * Fixed super init for base set from core sets, fixed update settings func, indent * Better missing log file handling * Add time delay to task doc reading (debug) * Account for "ERROR" tag being anywhere in log file, actual error in last two lines * Missing subprocess.wait() added, removed time.sleep debugger * More info in lammps error message * Add in check if force_field is given as a string to ensure species is not none * bug in reading error from log * Better defaults for friction factors, fixed bug in update_settings * Bug in update_settings * Problem with passing settings from core to base sets * Another attempt at fixing passing kwargs from core to base sets * Added option for boundary (periodic or otherwise) and dimensions of simulation * Made structure optional to make since a lammpsdata obj can also be provided as a write_input_set_kwarg * Added neighbour list recalc line, needed to stabilize real simulations * Cleaned up init logic a bit, kwargs now consistent with AseMDMaker, fixed bug of pop instead of get from force_field dict, add comment for kwarg explanations * Massive cleanup of core sets and makers, settings now internally consistent across sets * lintig * Error with not having temperature and pressure as props of set, account for not storing trajectory in task_doc * By default write trajectory parsed from dump as "trajectory{id}.traj" * Option to now give force_field as input to either the input_set_generator or the maker directly, better convinience for the user imo. Moved force_field setting code to a function for reusability. Type fixes. * Add ability to take in openff interchange as input for FF and topology (to be tested) * corrected logic when providing interchange as FF * New LammpsInterchange class to allow for serialization of Interchange, moved Interchange definition to utils, default value for force_field now needs a pair_style if an interchage is used as input (using lj/cut/coul/cut 10 as default pair_stlye for now) * Updated minimization template * Added warnings when user asks to store trajectory in jobstore, now defaults to not parsing dumps due to possilbe size * Added more settings keys to templates, made file names consistent between templates * Moved update_settings to be a part of BaseLammpsSet. More intuitive file structure, MDEnsemble typing for ensemble, update_settings is now called for updating both settings dict and input set properties * Removed print statement * Remove all extra attributes from BaseLammpsSet, use settings dict for attributes instead * Now also parse dump files as text and add to taskdoc, might be easier than converting to a trajectory object but still having access to the simulation output * Now also take in a LammpsInputFile object as a template, super helpful when making an input file using the pmg io functions * First iteration of adding tests for lammps flows. * Move the dealing with template type logic to pymatgen * Cleaned up tests and paths, removed some redundancies * Add warning if store trajectory is specified by user, gzip output dir on finishing job * Use io funcs from pmg to read in job's input files, changed trajectory to trajectories in task_doc * Remove unnecessary lines about len(trajecotry) from job tests, added test minimization set, test for reading task_doc * Linting and cleaned up doc strings * Save parsed dump files to jobstore instead of taskdoc * typing; minor changes to dump convertor class (now takes in index to ensure only a single frame can be read), final structure of lammps run is parsed regardless of storetrajectoryoption, helps with consistency in outputs and for chaining together jobs * removed uncessary imports, fixed test with partial trajecotry * Added flow for melt+quench+thermalize (standard for amorphous/glassy material generation), added tests for flows * MASSIVE UPDATE 1. Made a lot of changes to how settings are processed in the input sets. Moved to a pmg style inputsetgenerator instead of wrapping around the very limiting generator in pmg (hope to move this into pmg in a future commit) 2. All the important settings that the user typically specifies are in an object called LammpsInputSettings. User shouldnt have to interact with this ever as the inputsetgenerator applies whatever updates are needed through a class method. 3. Force fields are better treated now: user can now give the FF as a file of coefficients (or in the dict format from the previous version), and this file is written into the lammpsrun through an "include <file>" command (even if the user gives the coeffs directly). FF styles have to be specified beforehand in the maker (will put up a tutorial clarifying the formats soon). 4. bug fixes in taskdoc and basemaker 5. updated tests to reflect the new input set format * Updated paths for tests * Added CustomLammpsMaker, cleaned up comments * Remove unneccesary settigns_files * Added docstrings, cleaned up old util functions, fixed imports * Make customlammpsmaker more flexible, add kspace settings to input Roll back customlammpsmaker to earlier iteration, now updates to the provided template can be done using a $variable style * Allow all types of inputs to customslammpsmaker to provide settings updates * Move inputsetgenerator to pmg Moved the BaseLammpsInputGenerator to pymatgen.io.lammps to be consistent with how other input sets are placed * Modified jobs and flows to now be consistent with LammpsSettings obj in pmg, modified tests accordingly. * better FF handling * Better FF handling * Synced sets and makers with Templated approached based on BaseLammpsSetGenerator in pmg * Added a tutorial notebook to show how to initialize and use the Lammps workflows * Updated tutorial * Updated notebook * Updated tests Updated test files (not the actual tests) to be consistent with new pmg input sets * Fixed typo in lammpsjob decorator; fixed logic in taskdoc * Fixed typo in lammpsjob decorator; fixed logic in taskdoc * Removed unecessary files Removed all instances of the original LammpsInterchange (too difficult to configure correctly), and removed the templates since they've been moved to pymatgen * remove comment * fix bug of metadata going to jobstore * linting * Added maker for NVE jobs * Consolidate lammps run commands * fix lammps mpi invokation * fix mpirun/srun invokation * Fixed typo/formatting in tutorial * Post init fix in CustomLammpsMaker * Ruff, added ability to parse additional files that might be produced by the run * Made flows take in prev_dir to read restart files, npt_maker now gets default init, ruff * removed msonable requirement * Now store the any output files with *.data fomrat in order to retain topology info, fix return type in Dumpconverteer * moved all task doc settings out into taskdoc funcs, prev_dir can take str too * change return type to any * add molecule to type signatures * fix lint and sets init * fix bug in init * fix default args due to change in _BASE_LAMMPS_SETTINGS structure * update tests to work with new input structure * fix taskdoc.inputs to be processeable by montydecoder * store raw_log_file in taskdoc instead of jobstore * fix bug with storing final_structure in taskdoc; update default storetrajectory enum so saving of dump files can be turned off * define inputs/outputs earlier in taskdoc to avoid bug * add typing for LammpsForceField * update schema tests to reflect new storetrajectiory defaults * fix reading restart files that have been gzipped * updated lammps tutorial notebook to work with newest versions of flows * make all sets into dataclasses, redo init process to make it much more cleaner * update tests to work with new approach of passing in custom settings * updated lammps tutorial to work with new approach of passing in custom settings * added NPzAT lammps maker * added NPT set tests * update default traj store option to NO due to issues with storing even small dump files hitting the bson criteria * Fix tests to relfect new default for storing dump files * precommit * remove mystery traj file * skip lammps workflow in ci * review comments --------- Co-authored-by: Bryant Li <94406497+BryantLi-BLI@users.noreply.github.com> Co-authored-by: esoteric-ephemera <aaron.kaplan.physics@gmail.com>
1 parent 43d28d8 commit 9328a2b

78 files changed

Lines changed: 2002 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/testing.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ jobs:
290290
MP_API_KEY: ${{ secrets.MP_API_KEY }}
291291
run: |
292292
micromamba activate a2
293-
pytest -n auto --nbmake ./tutorials --ignore=./tutorials/openmm_tutorial.ipynb --ignore=./tutorials/force_fields --ignore=./tutorials/torchsim_tutorial.ipynb
293+
pytest -n auto --nbmake ./tutorials --ignore=./tutorials/openmm_tutorial.ipynb --ignore=./tutorials/force_fields --ignore=./tutorials/torchsim_tutorial.ipynb --ignore=./tutorials/lammps_workflow.ipynb
294294
295295
- name: Test ASE
296296
env:

src/atomate2/lammps/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Define LAMMPS jobs and workflows."""

src/atomate2/lammps/files.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""File I/O functions for LAMMPS input files."""
2+
3+
from pathlib import Path
4+
from typing import Any, Literal
5+
6+
from ase.io import Trajectory as AseTrajectory
7+
from ase.io import read
8+
from emmet.core.vasp.calculation import StoreTrajectoryOption
9+
from monty.serialization import dumpfn
10+
from numpy.typing import ArrayLike
11+
from pymatgen.core import Lattice, Molecule, Structure
12+
from pymatgen.core.trajectory import Trajectory as PmgTrajectory
13+
from pymatgen.io.ase import AseAtomsAdaptor
14+
from pymatgen.io.lammps.data import CombinedData, LammpsBox, LammpsData
15+
from pymatgen.io.lammps.generators import BaseLammpsGenerator
16+
17+
18+
def write_lammps_input_set(
19+
data: Structure | Molecule | LammpsData | CombinedData,
20+
input_set_generator: BaseLammpsGenerator,
21+
box_or_lattice: Lattice | LammpsBox | ArrayLike | None = None,
22+
additional_data: LammpsData | CombinedData | None = None,
23+
directory: str | Path = ".",
24+
) -> None:
25+
"""Write LAMMPS input set to a directory."""
26+
input_set = input_set_generator.get_input_set(
27+
data=data, additional_data=additional_data, box_or_lattice=box_or_lattice
28+
)
29+
input_set.write_input(directory)
30+
31+
32+
class DumpConvertor:
33+
"""
34+
Class to convert LAMMPS dump files to pymatgen or ase Trajectory objects.
35+
36+
args:
37+
dumpfile : str
38+
Path to the LAMMPS dump file
39+
store_md_outputs : StoreTrajectoryOption
40+
Option to store MD outputs in the Trajectory object
41+
read_index : str | int
42+
Index of the frame to read from the dump file
43+
(default is ':', i.e. read all frames).
44+
Use an integer to read a specific frame (practical for large files).
45+
46+
"""
47+
48+
def __init__(
49+
self,
50+
dumpfile: str,
51+
store_md_outputs: StoreTrajectoryOption = StoreTrajectoryOption.NO,
52+
read_index: str | int = ":",
53+
) -> None:
54+
self.store_md_outputs = store_md_outputs
55+
self.traj = (
56+
read(dumpfile, index=read_index)
57+
if isinstance(read_index, str)
58+
else [read(dumpfile, index=read_index)]
59+
)
60+
self.is_periodic = any(self.traj[0].pbc)
61+
self.frame_properties_keys = ["forces", "velocities"]
62+
63+
def to_ase_trajectory(self, filename: str | None = None) -> AseTrajectory:
64+
"""Convert to ASE trajectory object."""
65+
for idx, atoms in enumerate(self.traj):
66+
with AseTrajectory(
67+
filename, "a" if idx > 0 else "w", atoms=atoms
68+
) as file: # check logic here
69+
file.write()
70+
return AseTrajectory(filename, "r")
71+
72+
def to_pymatgen_trajectory(self, filename: str | None = None) -> PmgTrajectory:
73+
"""Convert to pymatgen trajectory object."""
74+
species = AseAtomsAdaptor.get_structure(
75+
self.traj[0], cls=Structure if self.is_periodic else Molecule
76+
).species
77+
78+
frames = []
79+
frame_properties = []
80+
81+
for atoms in self.traj:
82+
if self.store_md_outputs == StoreTrajectoryOption.FULL:
83+
frame_properties.append(
84+
{
85+
key: getattr(atoms, f"get_{key}")()
86+
for key in self.frame_properties_keys
87+
}
88+
)
89+
90+
if self.is_periodic:
91+
frames.append(
92+
Structure(
93+
lattice=atoms.get_cell(),
94+
species=species,
95+
coords=atoms.get_positions(),
96+
coords_are_cartesian=True,
97+
)
98+
)
99+
else:
100+
frames.append(
101+
Molecule(
102+
species=species,
103+
coords=atoms.get_positions(),
104+
charge=atoms.get_charges(),
105+
properties={"box": atoms.get_cell().tolist()},
106+
)
107+
)
108+
traj_method = "from_structures" if self.is_periodic else "from_molecules"
109+
pmg_traj = getattr(PmgTrajectory, traj_method)(
110+
frames,
111+
frame_properties=frame_properties or None,
112+
constant_lattice=False,
113+
)
114+
115+
if filename:
116+
dumpfn(pmg_traj, filename)
117+
118+
return pmg_traj
119+
120+
def save(
121+
self, filename: str | None = None, fmt: Literal["pmg", "ase"] = "pmg"
122+
) -> Any:
123+
"""Save the trajectory to a file."""
124+
filename = str(filename) if filename is not None else None
125+
if fmt == "pmg" and filename:
126+
return self.to_pymatgen_trajectory(filename=filename)
127+
if fmt == "ase" and filename:
128+
return self.to_ase_trajectory(filename=filename)
129+
return None
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Lammps flow makers for atomate2."""

src/atomate2/lammps/flows/core.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""Core LAMMPS flows."""
2+
3+
from copy import deepcopy
4+
from dataclasses import dataclass, field
5+
6+
from jobflow import Flow, Maker
7+
from pymatgen.core import Structure
8+
9+
from atomate2.lammps.jobs.base import BaseLammpsMaker
10+
from atomate2.lammps.jobs.core import LammpsNPTMaker, LammpsNVTMaker
11+
12+
13+
@dataclass
14+
class MeltQuenchThermalizeMaker(Maker):
15+
"""Melt -> Quench -> Thermalize flow maker."""
16+
17+
name: str = "melt-quench-thermalize"
18+
melt_maker: BaseLammpsMaker = field(default_factory=LammpsNPTMaker)
19+
quench_maker: BaseLammpsMaker = field(default_factory=LammpsNPTMaker)
20+
thermalize_maker: BaseLammpsMaker = field(default_factory=LammpsNVTMaker)
21+
22+
def make(self, structure: Structure) -> Flow:
23+
"""Make the flow for melting, quenching, and thermalizing a structure."""
24+
melt = self.melt_maker.make(structure)
25+
quench = self.quench_maker.make(
26+
melt.output.structure, prev_dir=melt.output.dir_name
27+
)
28+
thermalize = self.thermalize_maker.make(
29+
quench.output.structure, prev_dir=quench.output.dir_name
30+
)
31+
return Flow([melt, quench, thermalize], name=self.name)
32+
33+
@classmethod
34+
def from_temperature_steps(
35+
cls,
36+
npt_maker: LammpsNPTMaker,
37+
nvt_maker: LammpsNVTMaker = None,
38+
start_temperature: float = 300,
39+
melt_temperature: float = 3000,
40+
quench_temperature: float = 300,
41+
n_steps_melt: int = 10000,
42+
n_steps_quench: int = 10000,
43+
n_steps_thermalize: int = 10000,
44+
) -> "MeltQuenchThermalizeMaker":
45+
"""Make a melt-quench-thermalize flow maker from temperature and steps."""
46+
melt_maker = deepcopy(npt_maker)
47+
melt_maker.name = "melt"
48+
melt_maker.input_set_generator.update_settings(
49+
{
50+
"start_temp": start_temperature,
51+
"end_temp": melt_temperature,
52+
"nsteps": n_steps_melt,
53+
}
54+
)
55+
56+
quench_maker = deepcopy(npt_maker)
57+
quench_maker.name = "quench"
58+
quench_maker.input_set_generator.update_settings(
59+
{
60+
"start_temp": melt_temperature,
61+
"end_temp": quench_temperature,
62+
"nsteps": n_steps_quench,
63+
}
64+
)
65+
66+
thermalize_maker = deepcopy(nvt_maker) if nvt_maker else deepcopy(npt_maker)
67+
thermalize_maker.name = "thermalize"
68+
thermalize_maker.input_set_generator.update_settings(
69+
{
70+
"start_temp": quench_temperature,
71+
"end_temp": quench_temperature,
72+
"nsteps": n_steps_thermalize,
73+
}
74+
)
75+
return cls(
76+
melt_maker=melt_maker,
77+
quench_maker=quench_maker,
78+
thermalize_maker=thermalize_maker,
79+
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""Lammps job makers for atomate2."""
2+
3+
from .base import BaseLammpsMaker
4+
from .core import CustomLammpsMaker, LammpsNPTMaker, LammpsNVTMaker, MinimizationMaker

src/atomate2/lammps/jobs/base.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
"""Base job maker for LAMMPS calculations."""
2+
3+
import glob
4+
import os
5+
import warnings
6+
from collections.abc import Callable
7+
from dataclasses import dataclass, field
8+
from pathlib import Path
9+
10+
from emmet.core.vasp.task_valid import TaskState
11+
from jobflow import Maker, Response, job
12+
from pymatgen.core import Molecule, Structure
13+
from pymatgen.io.lammps.generators import (
14+
BaseLammpsSetGenerator,
15+
CombinedData,
16+
LammpsData,
17+
LammpsForceField,
18+
)
19+
20+
from atomate2.common.files import gunzip_files, gzip_files
21+
from atomate2.lammps.files import write_lammps_input_set
22+
from atomate2.lammps.run import run_lammps
23+
from atomate2.lammps.schemas.task import LammpsTaskDocument, StoreTrajectoryOption
24+
25+
_DATA_OBJECTS: list[str] = [
26+
"inputs",
27+
"trajectories",
28+
"dump_files",
29+
]
30+
31+
__all__ = ("BaseLammpsMaker", "lammps_job")
32+
33+
34+
class LammpsRunError(Exception):
35+
"""Custom exception for LAMMPS jobs."""
36+
37+
def __init__(self, message: str) -> None:
38+
super().__init__(message)
39+
self.message = message
40+
41+
42+
def lammps_job(method: Callable) -> job:
43+
"""Job decorator for LAMMPS jobs."""
44+
return job(method, data=_DATA_OBJECTS, output_schema=LammpsTaskDocument)
45+
46+
47+
@dataclass
48+
class BaseLammpsMaker(Maker):
49+
"""
50+
Basic Maker class for LAMMPS jobs.
51+
52+
name: str
53+
Name of the job
54+
input_set_generator: BaseLammpsGenerator
55+
Input set generator for the job, default is the BaseLammpsSetGenerator.
56+
Check the sets module for more options on input kwargs.
57+
write_input_set_kwargs: dict
58+
Additional kwargs to write_lammps_input_set
59+
run_lammps_kwargs: dict
60+
Additional kwargs to run_lammps
61+
task_document_kwargs: dict
62+
Additional kwargs to TaskDocument.from_directory
63+
write_additional_data: dict
64+
Additional data to write to the job directory
65+
"""
66+
67+
name: str = "Base LAMMPS job"
68+
input_set_generator: BaseLammpsSetGenerator = field(
69+
default_factory=BaseLammpsSetGenerator
70+
)
71+
force_field: str | dict | LammpsForceField | None = field(default=None)
72+
write_input_set_kwargs: dict = field(default_factory=dict)
73+
run_lammps_kwargs: dict = field(default_factory=dict)
74+
task_document_kwargs: dict = field(default_factory=dict)
75+
write_additional_data: LammpsData | CombinedData = field(default_factory=dict)
76+
77+
def __post_init__(self) -> None:
78+
"""Post-initialization warnings for the job."""
79+
if (
80+
self.task_document_kwargs.get("store_trajectory", StoreTrajectoryOption.NO)
81+
!= StoreTrajectoryOption.NO
82+
):
83+
warnings.warn(
84+
"Trajectory data might be large, only store if absolutely necessary. \
85+
Consider manually parsing the dump files instead.",
86+
stacklevel=1,
87+
)
88+
89+
if self.force_field:
90+
if isinstance(self.force_field, dict):
91+
self.force_field = LammpsForceField.from_dict(self.force_field)
92+
self.input_set_generator.force_field = self.force_field
93+
94+
@lammps_job
95+
def make(
96+
self,
97+
input_structure: Structure | Molecule | Path | LammpsData = None,
98+
prev_dir: Path | str = None,
99+
) -> Response:
100+
"""Run a LAMMPS calculation."""
101+
if prev_dir:
102+
restart_files = glob.glob(os.path.join(prev_dir, "*restart*"))
103+
if len(restart_files) != 1:
104+
raise FileNotFoundError(
105+
"No/More than one restart file found in the previous directory. \
106+
If present, it should have the extension '.restart'!"
107+
)
108+
109+
restart_file = restart_files[0]
110+
if restart_file.endswith(".restart.gz"):
111+
gunzip_files(
112+
directory=prev_dir, include_files=[restart_file], force=True
113+
)
114+
restart_file = str(Path(restart_file).with_suffix(""))
115+
self.input_set_generator.update_settings({"read_restart": restart_file})
116+
117+
if isinstance(input_structure, Path):
118+
input_structure = LammpsData.from_file(
119+
input_structure,
120+
atom_style=self.input_set_generator.settings.get("atom_style", "full"),
121+
)
122+
123+
write_lammps_input_set(
124+
data=input_structure,
125+
input_set_generator=self.input_set_generator,
126+
additional_data=self.write_additional_data,
127+
**self.write_input_set_kwargs,
128+
)
129+
130+
run_lammps(**self.run_lammps_kwargs)
131+
132+
task_doc = LammpsTaskDocument.from_directory(
133+
os.getcwd(), task_label=self.name, **self.task_document_kwargs
134+
)
135+
136+
if task_doc.state == TaskState.ERROR:
137+
try:
138+
error = ""
139+
for index, line in enumerate(task_doc.raw_log_file.split("\n")):
140+
if "ERROR" in line:
141+
error = error.join(task_doc.raw_log_file.split("\n")[index:])
142+
break
143+
except ValueError:
144+
error = "could not parse log file"
145+
raise LammpsRunError(f"Task {task_doc.task_label} failed, error: {error}")
146+
147+
# TODO: Only gzip LAMMPS files, not job scheduler related files
148+
gzip_files(".")
149+
150+
return Response(output=task_doc)

0 commit comments

Comments
 (0)