forked from stfc/aiida-mlip
-
Notifications
You must be signed in to change notification settings - Fork 0
/
md_parser.py
116 lines (92 loc) · 3.63 KB
/
md_parser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""
MD parser.
"""
from pathlib import Path
import yaml
from aiida.common import exceptions
from aiida.engine import ExitCode
from aiida.orm import Dict, SinglefileData
from aiida.orm.nodes.process.process import ProcessNode
from aiida.plugins import CalculationFactory
from aiida_mlip.calculations.md import MD
from aiida_mlip.helpers.converters import xyz_to_aiida_traj
from aiida_mlip.parsers.base_parser import BaseParser
MDCalculation = CalculationFactory("mlip.md")
class MDParser(BaseParser):
"""
Parser class for parsing output of molecular dynamics simulation.
Inherits from SPParser.
Parameters
----------
node : aiida.orm.nodes.process.process.ProcessNode
ProcessNode of calculation.
Methods
-------
parse(**kwargs: Any) -> int:
Parse outputs, store results in the database.
Returns
-------
int
An exit code.
Raises
------
exceptions.ParsingError
If the ProcessNode being passed was not produced by a `MD`.
"""
def __init__(self, node: ProcessNode):
"""
Check that the ProcessNode being passed was produced by a `MD`.
Parameters
----------
node : aiida.orm.nodes.process.process.ProcessNode
ProcessNode of calculation.
"""
super().__init__(node)
if not issubclass(node.process_class, MDCalculation):
raise exceptions.ParsingError("Can only parse `MD` calculations")
def parse(self, **kwargs) -> ExitCode:
"""
Parse outputs, store results in the database.
Parameters
----------
**kwargs : Any
Any keyword arguments.
Returns
-------
int
An exit code.
"""
# Call the parent parse method to handle common parsing logic
exit_code = super().parse(**kwargs)
if exit_code != ExitCode(0):
return exit_code
md_dictionary = self.node.inputs.md_kwargs.get_dict()
# Process trajectory file saving both the file and trajectory as aiida data
traj_filepath = md_dictionary.get("traj-file", MD.DEFAULT_TRAJ_FILE)
with self.retrieved.open(traj_filepath, "rb") as handle:
self.out("traj_file", SinglefileData(file=handle, filename=traj_filepath))
final_str, traj_output = xyz_to_aiida_traj(
Path(self.node.get_remote_workdir(), traj_filepath)
)
self.out("traj_output", traj_output)
self.out("final_structure", final_str)
# Process stats file as singlefiledata
stats_filepath = md_dictionary.get("stats-file", MD.DEFAULT_STATS_FILE)
with self.retrieved.open(stats_filepath, "rb") as handle:
self.out("stats_file", SinglefileData(file=handle, filename=stats_filepath))
# Process summary as both singlefiledata and results dictionary
summary_filepath = md_dictionary.get("summary", MD.DEFAULT_SUMMARY_FILE)
print(self.node.get_remote_workdir(), summary_filepath)
with self.retrieved.open(summary_filepath, "rb") as handle:
self.out("summary", SinglefileData(file=handle, filename=summary_filepath))
with self.retrieved.open(summary_filepath, "r") as handle:
try:
res_dict = yaml.safe_load(handle.read())
except yaml.YAMLError as exc:
print("Error loading YAML:", exc)
if res_dict is None:
self.logger.error("Results dictionary empty")
return self.exit_codes.ERROR_MISSING_OUTPUT_FILES
results_node = Dict(res_dict)
self.out("results_dict", results_node)
return ExitCode(0)