-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add the `LammpsRawCalculation` and `LammpsRawParser` plugins These `CalcJob` and `Parser` plugins provide a bare-bones interface to running any LAMMPS calculation. The `CalcJob` just requires the `script` input which takes a complete LAMMPS input script. The `files` namespace can be used to add additional input files to the working directory. * Docs: Add `CalcJobNode` to the nitpick exceptions
- Loading branch information
Showing
12 changed files
with
542 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
"""Plugin with minimal interface to run LAMMPS.""" | ||
import shutil | ||
|
||
from aiida import orm | ||
from aiida.common.datastructures import CalcInfo, CodeInfo | ||
from aiida.common.folders import Folder | ||
from aiida.engine import CalcJob | ||
|
||
|
||
class LammpsRawCalculation(CalcJob): | ||
"""Plugin with minimal interface to run LAMMPS.""" | ||
|
||
FILENAME_INPUT = "input.in" | ||
FILENAME_OUTPUT = "lammps.out" | ||
FILENAME_LOG = "lammps.log" | ||
|
||
@classmethod | ||
def define(cls, spec): | ||
super().define(spec) | ||
spec.input( | ||
"script", | ||
valid_type=orm.SinglefileData, | ||
help="Complete input script to use. If specified, `structure`, `potential` and `parameters` are ignored.", | ||
) | ||
spec.input_namespace( | ||
"files", | ||
valid_type=orm.SinglefileData, | ||
required=False, | ||
help="Optional files that should be written to the working directory.", | ||
) | ||
spec.input( | ||
"filenames", | ||
valid_type=orm.Dict, | ||
serializer=orm.to_aiida_type, | ||
required=False, | ||
help="Optional namespace to specify with which filenames the files of ``files`` input should be written.", | ||
) | ||
spec.inputs["metadata"]["options"][ | ||
"input_filename" | ||
].default = cls.FILENAME_INPUT | ||
spec.inputs["metadata"]["options"][ | ||
"output_filename" | ||
].default = cls.FILENAME_OUTPUT | ||
spec.inputs["metadata"]["options"]["parser_name"].default = "lammps.raw" | ||
spec.inputs.validator = cls.validate_inputs | ||
|
||
spec.output( | ||
"results", | ||
valid_type=orm.Dict, | ||
required=True, | ||
help="The data extracted from the lammps log file", | ||
) | ||
spec.exit_code( | ||
351, | ||
"ERROR_LOG_FILE_MISSING", | ||
message="the file with the lammps log was not found", | ||
invalidates_cache=True, | ||
) | ||
spec.exit_code( | ||
1001, | ||
"ERROR_PARSING_LOGFILE", | ||
message="parsing the log file has failed.", | ||
) | ||
|
||
@classmethod | ||
def validate_inputs(cls, value, ctx): | ||
"""Validate the top-level inputs namespace.""" | ||
# The filename with which the file is written to the working directory is defined by the ``filenames`` input | ||
# namespace, falling back to the filename of the ``SinglefileData`` node if not defined. | ||
overrides = value["filenames"].get_dict() if "filenames" in value else {} | ||
filenames = [ | ||
overrides.get(key, node.filename) | ||
for key, node in value.get("files", {}).items() | ||
] | ||
|
||
if len(filenames) != len(set(filenames)): | ||
return ( | ||
f"The list of filenames of the ``files`` input is not unique: {filenames}. Use the ``filenames`` input " | ||
"namespace to explicitly define unique filenames for each file." | ||
) | ||
|
||
def prepare_for_submission(self, folder: Folder) -> CalcInfo: | ||
"""Prepare the calculation for submission. | ||
:param folder: A temporary folder on the local file system. | ||
:returns: A :class:`aiida.common.datastructures.CalcInfo` instance. | ||
""" | ||
filename_log = self.FILENAME_LOG | ||
filename_input = self.inputs.metadata.options.input_filename | ||
filename_output = self.inputs.metadata.options.output_filename | ||
filenames = ( | ||
self.inputs["filenames"].get_dict() if "filenames" in self.inputs else {} | ||
) | ||
provenance_exclude_list = [] | ||
|
||
with folder.open(filename_input, "w") as handle: | ||
handle.write(self.inputs.script.get_content()) | ||
|
||
for key, node in self.inputs.get("files", {}).items(): | ||
|
||
# The filename with which the file is written to the working directory is defined by the ``filenames`` input | ||
# namespace, falling back to the filename of the ``SinglefileData`` node if not defined. | ||
filename = filenames.get(key, node.filename) | ||
|
||
with folder.open(filename, "wb") as target: | ||
with node.open(mode="rb") as source: | ||
shutil.copyfileobj(source, target) | ||
|
||
provenance_exclude_list.append(filename) | ||
|
||
codeinfo = CodeInfo() | ||
codeinfo.cmdline_params = ["-in", filename_input, "-log", filename_log] | ||
codeinfo.code_uuid = self.inputs.code.uuid | ||
codeinfo.stdout_name = self.inputs.metadata.options.output_filename | ||
|
||
calcinfo = CalcInfo() | ||
calcinfo.provenance_exclude_list = provenance_exclude_list | ||
calcinfo.retrieve_list = [filename_output, filename_log] | ||
calcinfo.codes_info = [codeinfo] | ||
|
||
return calcinfo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
"""Base parser for LAMMPS log output.""" | ||
import time | ||
|
||
from aiida import orm | ||
from aiida.parsers.parser import Parser | ||
|
||
from aiida_lammps.calculations.raw import LammpsRawCalculation | ||
from aiida_lammps.parsers.parse_raw import parse_logfile | ||
|
||
|
||
class LammpsRawParser(Parser): | ||
"""Base parser for LAMMPS log output.""" | ||
|
||
def parse(self, **kwargs): | ||
"""Parse the contents of the output files stored in the ``retrieved`` output node.""" | ||
retrieved = self.retrieved | ||
retrieved_filenames = retrieved.base.repository.list_object_names() | ||
filename_log = LammpsRawCalculation.FILENAME_LOG | ||
|
||
if filename_log not in retrieved_filenames: | ||
return self.exit_codes.ERROR_LOG_FILE_MISSING | ||
|
||
parsed_data = parse_logfile( | ||
file_contents=retrieved.base.repository.get_object_content(filename_log) | ||
) | ||
if parsed_data is None: | ||
return self.exit_codes.ERROR_PARSING_LOGFILE | ||
|
||
global_data = parsed_data["global"] | ||
results = {"compute_variables": global_data} | ||
|
||
if "total_wall_time" in global_data: | ||
try: | ||
parsed_time = time.strptime(global_data["total_wall_time"], "%H:%M:%S") | ||
except ValueError: | ||
pass | ||
else: | ||
total_wall_time_seconds = ( | ||
parsed_time.tm_hour * 3600 | ||
+ parsed_time.tm_min * 60 | ||
+ parsed_time.tm_sec | ||
) | ||
global_data["total_wall_time_seconds"] = total_wall_time_seconds | ||
|
||
self.out("results", orm.Dict(results)) | ||
|
||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
"""Run a LAMMPS calculation with additional input files | ||
The example input script is taken from https://www.lammps.org/inputs/in.rhodo.txt and is an example benchmark script for | ||
the official benchmarks of LAMMPS. It is a simple MD simulation of a protein. It requires an additional input file in | ||
the working directory ``data.rhodo``. This example shows how to add such additional input files. | ||
""" | ||
import io | ||
import textwrap | ||
|
||
from aiida import engine, orm, plugins | ||
|
||
script = orm.SinglefileData( | ||
io.StringIO( | ||
textwrap.dedent( | ||
""" | ||
# Rhodopsin model | ||
units real | ||
neigh_modify delay 5 every 1 | ||
atom_style full | ||
bond_style harmonic | ||
angle_style charmm | ||
dihedral_style charmm | ||
improper_style harmonic | ||
pair_style lj/charmm/coul/long 8.0 10.0 | ||
pair_modify mix arithmetic | ||
kspace_style pppm 1e-4 | ||
read_data data.rhodo | ||
fix 1 all shake 0.0001 5 0 m 1.0 a 232 | ||
fix 2 all npt temp 300.0 300.0 100.0 & | ||
z 0.0 0.0 1000.0 mtk no pchain 0 tchain 1 | ||
special_bonds charmm | ||
thermo 50 | ||
thermo_style multi | ||
timestep 2.0 | ||
run 100 | ||
""" | ||
) | ||
) | ||
) | ||
data = orm.SinglefileData( | ||
io.StringIO( | ||
textwrap.dedent( | ||
""" | ||
LAMMPS data file from restart file: timestep = 5000, procs = 1 | ||
32000 atoms | ||
27723 bonds | ||
40467 angles | ||
56829 dihedrals | ||
1034 impropers | ||
... | ||
""" | ||
) | ||
) | ||
) | ||
|
||
builder = plugins.CalculationFactory("lammps.raw").get_builder() | ||
builder.code = orm.load_code("lammps-23.06.2022@localhost") | ||
builder.script = script | ||
builder.files = {"data": data} | ||
builder.filenames = {"data": "data.rhodo"} | ||
builder.metadata.options = {"resources": {"num_machines": 1}} | ||
_, node = engine.run_get_node(builder) | ||
|
||
print(f"Calculation node: {submission_node}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Oops, something went wrong.