Coverage for biobb_amber / pmemd / pmemd_mdrun.py: 9%
180 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-15 15:57 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-15 15:57 +0000
1#!/usr/bin/env python3
3"""Module containing the PmemdMDRun class and the command line interface."""
4from typing import Optional
5import re
6from pathlib import Path
7from biobb_common.generic.biobb_object import BiobbObject
8from biobb_common.tools import file_utils as fu
9from biobb_common.tools.file_utils import launchlogger
10from biobb_amber.pmemd.common import check_input_path, check_output_path
13class PmemdMDRun(BiobbObject):
14 """
15 | biobb_amber PmemdMDRun
16 | Wrapper of the `AmberTools (AMBER MD Package) pmemd tool <https://ambermd.org/AmberTools.php>`_ module.
17 | Runs molecular dynamics using pmemd tool from the AMBER MD package.
19 Args:
20 input_top_path (str): Input topology file (AMBER ParmTop). File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/pmemd/cln025.prmtop>`_. Accepted formats: top (edam:format_3881), parmtop (edam:format_3881), prmtop (edam:format_3881).
21 input_crd_path (str): Input coordinates file (AMBER crd). File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/pmemd/cln025.inpcrd>`_. Accepted formats: crd (edam:format_3878), mdcrd (edam:format_3878), inpcrd (edam:format_3878), rst (edam:format_3886), rst7 (edam:format_3886), netcdf (edam:format_3650), nc (edam:format_3650), ncrst (edam:format_3886).
22 input_mdin_path (str) (Optional): Input configuration file (MD run options) (AMBER mdin). File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/pmemd/npt.mdin>`_. Accepted formats: mdin (edam:format_2330), in (edam:format_2330), txt (edam:format_2330).
23 input_cpin_path (str) (Optional): Input constant pH file (AMBER cpin). File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/pmemd/cln025.cpin>`_. Accepted formats: cpin (edam:format_2330).
24 input_ref_path (str) (Optional): Input reference coordinates for position restraints. File type: input. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/data/pmemd/sander.rst>`_. Accepted formats: crd (edam:format_3878), mdcrd (edam:format_3878), inpcrd (edam:format_3878), rst (edam:format_3886), rst7 (edam:format_3886), netcdf (edam:format_3650), nc (edam:format_3650), ncrst (edam:format_3886).
25 output_log_path (str): Output log file. File type: output. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/reference/pmemd/sander.log>`_. Accepted formats: log (edam:format_2330), out (edam:format_2330), txt (edam:format_2330), o (edam:format_2330).
26 output_traj_path (str): Output trajectory file. File type: output. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/reference/pmemd/sander.x>`_. Accepted formats: trj (edam:format_3878), crd (edam:format_3878), mdcrd (edam:format_3878), x (edam:format_3878), netcdf (edam:format_3650), nc (edam:format_3650).
27 output_rst_path (str): Output restart file. File type: output. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/reference/pmemd/sander.rst>`_. Accepted formats: rst (edam:format_3886), rst7 (edam:format_3886), netcdf (edam:format_3650), nc (edam:format_3650), ncrst (edam:format_3886).
28 output_cpout_path (str) (Optional): Output constant pH file (AMBER cpout). File type: output. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/reference/pmemd/sander.cpout>`_. Accepted formats: cpout (edam:format_2330).
29 output_cprst_path (str) (Optional): Output constant pH restart file (AMBER rstout). File type: output. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/reference/pmemd/sander.cprst>`_. Accepted formats: cprst (edam:format_3886), rst (edam:format_3886), rst7 (edam:format_3886).
30 output_mdinfo_path (str) (Optional): Output MD info. File type: output. `Sample file <https://github.com/bioexcel/biobb_amber/raw/master/biobb_amber/test/reference/pmemd/sander.mdinfo>`_. Accepted formats: mdinfo (edam:format_2330).
31 properties (dict - Python dictionary object containing the tool parameters, not input/output files):
32 * **mdin** (*dict*) - ({}) pmemd MD run options specification. (Used if *input_mdin_path* is None)
33 * **binary_path** (*str*) - ("pmemd") pmemd binary path to be used.
34 * **simulation_type** (*str*) - ("minimization") Default options for the mdin file. Each creates a different mdin file. Values: `minimization <https://biobb-amber.readthedocs.io/en/latest/_static/mdin/minimization.mdin>`_ (Runs an energy minimization), `min_vacuo <https://biobb-amber.readthedocs.io/en/latest/_static/mdin/min_vacuo.mdin>`_ (Runs an energy minimization in vacuo), `NVT <https://biobb-amber.readthedocs.io/en/latest/_static/mdin/NVT.mdin>`_ (Runs an NVT equilibration), `npt <https://biobb-amber.readthedocs.io/en/latest/_static/mdin/NPT.mdin>`_ (Runs an NPT equilibration), `free <https://biobb-amber.readthedocs.io/en/latest/_static/mdin/free.mdin>`_ (Runs a MD simulation).
35 * **mpi_bin** (*str*) - (None) Path to the MPI runner. Usually "mpirun" or "srun".
36 * **mpi_np** (*int*) - (0) [0~1000|1] Number of MPI processes. Usually an integer bigger than 1.
37 * **mpi_flags** (*str*) - (None) Path to the MPI hostlist file.
38 * **remove_tmp** (*bool*) - (True) [WF property] Remove temporal files.
39 * **restart** (*bool*) - (False) [WF property] Do not execute if output files exist.
40 * **sandbox_path** (*str*) - ("./") [WF property] Parent path to the sandbox directory.
42 Examples:
43 This is a use example of how to use the building block from Python::
45 from biobb_amber.pmemd.pmemd_mdrun import pmemd_mdrun
46 prop = {
47 'simulation_type' : 'npt',
48 'mdin' : {
49 'dt' : 0.002
50 }
51 }
52 pmemd_mdrun(input_top_path='/path/to/topology.top',
53 input_crd_path='/path/to/coordinates.crd',
54 output_traj_path='/path/to/newTrajectory.crd',
55 output_rst_path='/path/to/newRestart.rst',
56 output_log_path='/path/to/newAmberlog.log',
57 properties=prop)
59 Info:
60 * wrapped_software:
61 * name: AMBER pmemd
62 * version: >20
63 * license: other
64 * multinode: mpi
65 * ontology:
66 * name: EDAM
67 * schema: http://edamontology.org/EDAM.owl
69 """
71 def __init__(self, input_top_path: str, input_crd_path: str, output_log_path: str, output_traj_path: str, output_rst_path: str,
72 input_ref_path: Optional[str] = None, input_mdin_path: Optional[str] = None, input_cpin_path: Optional[str] = None, output_cpout_path: Optional[str] = None, output_cprst_path: Optional[str] = None, output_mdinfo_path: Optional[str] = None,
73 properties: Optional[dict] = None, **kwargs) -> None:
75 properties = properties or {}
77 # Call parent class constructor
78 super().__init__(properties)
79 self.locals_var_dict = locals().copy()
81 # Input/Output files
82 self.io_dict = {
83 'in': {'input_top_path': input_top_path,
84 'input_crd_path': input_crd_path,
85 'input_mdin_path': input_mdin_path,
86 'input_ref_path': input_ref_path,
87 'input_cpin_path': input_cpin_path},
88 'out': {'output_log_path': output_log_path,
89 'output_traj_path': output_traj_path,
90 'output_rst_path': output_rst_path,
91 'output_cpout_path': output_cpout_path,
92 'output_cprst_path': output_cprst_path,
93 'output_mdinfo_path': output_mdinfo_path}
94 }
96 # Properties specific for BB
97 self.properties = properties
98 self.simulation_type = properties.get('simulation_type', "minimization")
99 self.binary_path = properties.get('binary_path', "pmemd")
100 self.mdin = {k: str(v) for k, v in properties.get('mdin', dict()).items()}
102 # Properties for MPI
103 self.mpi_bin = properties.get('mpi_bin')
104 self.mpi_np = properties.get('mpi_np')
105 self.mpi_flags = properties.get('mpi_flags')
107 # Check the properties
108 self.check_properties(properties)
109 self.check_arguments()
111 def check_data_params(self, out_log, err_log):
112 """ Checks input/output paths correctness """
114 # Check input(s)
115 self.io_dict["in"]["input_top_path"] = check_input_path(self.io_dict["in"]["input_top_path"], "input_top_path", False, out_log, self.__class__.__name__)
116 self.io_dict["in"]["input_crd_path"] = check_input_path(self.io_dict["in"]["input_crd_path"], "input_crd_path", False, out_log, self.__class__.__name__)
117 self.io_dict["in"]["input_mdin_path"] = check_input_path(self.io_dict["in"]["input_mdin_path"], "input_mdin_path", True, out_log, self.__class__.__name__)
118 self.io_dict["in"]["input_cpin_path"] = check_input_path(self.io_dict["in"]["input_cpin_path"], "input_cpin_path", True, out_log, self.__class__.__name__)
119 self.io_dict["in"]["input_ref_path"] = check_input_path(self.io_dict["in"]["input_ref_path"], "input_ref_path", True, out_log, self.__class__.__name__)
121 # Check output(s)
122 self.io_dict["out"]["output_log_path"] = check_output_path(self.io_dict["out"]["output_log_path"], "output_log_path", False, out_log, self.__class__.__name__)
123 self.io_dict["out"]["output_traj_path"] = check_output_path(self.io_dict["out"]["output_traj_path"], "output_traj_path", False, out_log, self.__class__.__name__)
124 self.io_dict["out"]["output_rst_path"] = check_output_path(self.io_dict["out"]["output_rst_path"], "output_rst_path", False, out_log, self.__class__.__name__)
125 self.io_dict["out"]["output_cpout_path"] = check_output_path(self.io_dict["out"]["output_cpout_path"], "output_cpout_path", True, out_log, self.__class__.__name__)
126 self.io_dict["out"]["output_cprst_path"] = check_output_path(self.io_dict["out"]["output_cprst_path"], "output_cprst_path", True, out_log, self.__class__.__name__)
127 self.io_dict["out"]["output_mdinfo_path"] = check_output_path(self.io_dict["out"]["output_mdinfo_path"], "output_mdinfo_path", True, out_log, self.__class__.__name__)
129 def create_mdin(self, path: Optional[str] = None) -> str:
130 """Creates an AMBER MD configuration file (mdin) using the properties file settings"""
131 mdin_list = []
132 mdin_firstPart = []
133 mdin_middlePart = []
134 mdin_lastPart = []
136 self.output_mdin_path = path
138 if self.io_dict['in']['input_mdin_path']:
139 # MDIN parameters read from an input mdin file
140 mdin_firstPart.append("Mdin read from input file: " + self.io_dict['in']['input_mdin_path'])
141 mdin_firstPart.append("and modified by the biobb_amber module from the BioBB library ")
142 with open(self.io_dict['in']['input_mdin_path']) as input_params:
143 firstPart = True
144 secondPart = False
145 for line in input_params:
146 if '=' in line and not secondPart:
147 firstPart = False
148 mdin_middlePart.append(line.rstrip())
149 else:
150 if (firstPart):
151 mdin_firstPart.append(line.rstrip())
152 elif (secondPart):
153 mdin_lastPart.append(line.rstrip())
154 else:
155 secondPart = True
156 mdin_lastPart.append(line.rstrip())
158 for line in mdin_middlePart:
159 if ('!' in line or '#' in line) and not ('!@' in line or '!:' in line):
160 # Parsing lines with comments (#,!), e.g. :
161 # ntc=2, ntf=2, ! SHAKE, constrain lenghts of the bonds having H
162 params = re.split('!|#', line)
163 for param in params[0].split(','):
164 if param.strip():
165 mdin_list.append(" " + param.strip() + " ! " + params[1])
166 elif ('@' in line or ':' in line):
167 # Parsing masks, e.g. :
168 # restraintmask = ":1-40@P,O5',C5',C4',C3',O3'", restraint_wt = 0.5
169 mylist = re.findall(r'(?:[^,"]|"(?:\\.|[^"])*")+', line)
170 [mdin_list.append(" " + i.lstrip()) for i in mylist] # type: ignore
171 else:
172 for param in line.split(','):
173 if param.strip():
174 if not param.strip().startswith('!'):
175 mdin_list.append(" " + param.strip())
177 else:
178 # MDIN parameters added by the biobb_amber module
179 mdin_list.append("This mdin file has been created by the biobb_amber module from the BioBB library ")
181 sim_type = self.properties.get('simulation_type', 'minimization')
182 # sim_type = self.mdin.get('simulation_type', 'minimization')
183 minimization = (sim_type == 'minimization')
184 min_vacuo = (sim_type == 'min_vacuo')
185 heat = (sim_type == 'heat')
186 nvt = (sim_type == 'nvt')
187 npt = (sim_type == 'npt')
188 free = (sim_type == 'free')
189 md = (nvt or npt or free or heat)
191 mdin_list.append("Type of mdin: " + sim_type)
192 mdin_list.append("&cntrl")
194 # Pre-configured simulation type parameters
195 if minimization:
196 mdin_list.append(" imin = 1 ! BioBB simulation_type minimization")
197 if min_vacuo:
198 mdin_list.append(" imin = 1 ! BioBB simulation_type min_vacuo")
199 mdin_list.append(" ncyc = 250 ! BioBB simulation_type min_vacuo")
200 mdin_list.append(" ntb = 0 ! BioBB simulation_type min_vacuo")
201 mdin_list.append(" igb = 0 ! BioBB simulation_type min_vacuo")
202 mdin_list.append(" cut = 12 ! BioBB simulation_type min_vacuo")
203 if md:
204 mdin_list.append(" imin = 0 ! BioBB simulation_type nvt|npt|free|heat")
205 mdin_list.append(" cut = 10.0 ! BioBB simulation_type nvt|npt|free|heat")
206 mdin_list.append(" ntr = 0 ! BioBB simulation_type nvt|npt|free|heat")
207 mdin_list.append(" ntc = 2 ! BioBB simulation_type nvt|npt|free|heat")
208 mdin_list.append(" ntf = 2 ! BioBB simulation_type nvt|npt|free|heat")
209 mdin_list.append(" ntt = 3 ! BioBB simulation_type nvt|npt|free|heat")
210 mdin_list.append(" ig = -1 ! BioBB simulation_type nvt|npt|free|heat")
211 mdin_list.append(" ioutfm = 1 ! BioBB simulation_type nvt|npt|free|heat")
212 mdin_list.append(" iwrap = 1 ! BioBB simulation_type nvt|npt|free|heat")
213 mdin_list.append(" nstlim = 5000 ! BioBB simulation_type nvt|npt|free|heat")
214 mdin_list.append(" dt = 0.002 ! BioBB simulation_type nvt|npt|free|heat")
215 if npt:
216 mdin_list.append(" irest = 1 ! BioBB simulation_type npt")
217 mdin_list.append(" gamma_ln = 5.0 ! BioBB simulation_type npt")
218 mdin_list.append(" pres0 = 1.0 ! BioBB simulation_type npt")
219 mdin_list.append(" ntp = 1 ! BioBB simulation_type npt")
220 mdin_list.append(" taup = 2.0 ! BioBB simulation_type npt")
221 mdin_list.append(" ntx = 5 ! BioBB simulation_type npt")
222 if nvt:
223 mdin_list.append(" irest = 1 ! BioBB simulation_type nvt")
224 mdin_list.append(" gamma_ln = 5.0 ! BioBB simulation_type nvt")
225 mdin_list.append(" ntb = 1 ! BioBB simulation_type nvt")
226 mdin_list.append(" ntx = 5 ! BioBB simulation_type nvt")
227 if heat:
228 mdin_list.append(" tempi = 0.0 ! BioBB simulation_type heat")
229 mdin_list.append(" temp0 = 300.0 ! BioBB simulation_type heat")
230 mdin_list.append(" irest = 0 ! BioBB simulation_type heat")
231 mdin_list.append(" ntb = 1 ! BioBB simulation_type heat") # periodic boundaries
232 mdin_list.append(" gamma_ln = 1.0 ! BioBB simulation_type heat")
233 # mdin_list.append(" nmropt = 1 ! BioBB simulation_type heat")
235 # mdin_lastPart.append("/")
236 # mdin_lastPart.append("&wt")
237 # mdin_lastPart.append(" TYPE = 'TEMP0' ! BioBB simulation_type heat")
238 # mdin_lastPart.append(" ISTEP1 = 1 ! BioBB simulation_type heat")
239 # mdin_lastPart.append(" ISTEP2 = 4000 ! BioBB simulation_type heat")
240 # mdin_lastPart.append(" VALUE1 = 10.0 ! BioBB simulation_type heat")
241 # mdin_lastPart.append(" VALUE2 = 300.0 ! BioBB simulation_type heat")
242 # mdin_lastPart.append("/")
243 # mdin_lastPart.append("&wt")
244 # mdin_lastPart.append(" TYPE = 'END' ! BioBB simulation_type heat")
245 # mdin_lastPart.append("/")
247 # Adding the rest of parameters in the config file to the mdin file
248 # if the parameter has already been added replace the value
249 parameter_keys = [parameter.split('=')[0].strip() for parameter in mdin_list]
250 for k, v in self.mdin.items():
251 config_parameter_key = str(k).strip()
252 if config_parameter_key in parameter_keys:
253 mdin_list[parameter_keys.index(config_parameter_key)] = ' ' + config_parameter_key + ' = ' + str(v) + ' ! BioBB property'
254 else:
255 mdin_list.append(' ' + config_parameter_key + ' = '+str(v) + ' ! BioBB property')
257 # Writing MD configuration file (mdin)
258 with open(str(self.output_mdin_path), 'w') as mdin:
259 # Start of file keyword(s)
260 if mdin_firstPart:
261 for line in mdin_firstPart:
262 mdin.write(line + '\n')
264 # MD config parameters
265 for line in mdin_list:
266 mdin.write(line + '\n')
268 # End of file keyword(s)
269 if mdin_lastPart:
270 for line in mdin_lastPart:
271 mdin.write(line + '\n')
272 else:
273 mdin.write("&end\n")
275 return str(self.output_mdin_path)
277 @launchlogger
278 def launch(self):
279 """Launches the execution of the PmemdMDRun module."""
281 # check input/output paths and parameters
282 self.check_data_params(self.out_log, self.err_log)
284 # Setup Biobb
285 if self.check_restart():
286 return 0
287 self.stage_files()
289 # Creating temporary folder
290 self.tmp_folder = fu.create_unique_dir()
291 fu.log('Creating %s temporary folder' % self.tmp_folder, self.out_log)
293 # if self.io_dict['in']['input_mdin_path']:
294 # self.output_mdin_path = self.io_dict['in']['input_mdin_path']
295 # else:
296 # self.output_mdin_path = self.create_mdin(path=str(Path(self.tmp_folder).joinpath("pmemd.mdin")))
297 self.output_mdin_path = self.create_mdin(path=str(Path(self.tmp_folder).joinpath("pmemd.mdin")))
299 # Command line
300 # pmemd -O -i mdin/min.mdin -p $1.cpH.prmtop -c ph$i/$1.inpcrd -r ph$i/$1.min.rst7 -o ph$i/$1.min.o
301 self.cmd = [self.binary_path,
302 '-O',
303 '-i', self.output_mdin_path,
304 '-p', self.io_dict['in']['input_top_path'],
305 '-c', self.io_dict['in']['input_crd_path'],
306 '-r', self.io_dict['out']['output_rst_path'],
307 '-o', self.io_dict['out']['output_log_path'],
308 '-x', self.io_dict['out']['output_traj_path']
309 ]
311 if self.io_dict['in']['input_ref_path']:
312 self.cmd.append('-ref')
313 self.cmd.append(self.io_dict['in']['input_ref_path'])
315 if self.io_dict['in']['input_cpin_path']:
316 self.cmd.append('-cpin')
317 self.cmd.append(self.io_dict['in']['input_cpin_path'])
319 if self.io_dict['out']['output_mdinfo_path']:
320 self.cmd.append('-inf')
321 self.cmd.append(self.io_dict['out']['output_mdinfo_path'])
323 if self.io_dict['out']['output_cpout_path']:
324 self.cmd.append('-cpout')
325 self.cmd.append(self.io_dict['out']['output_cpout_path'])
327 if self.io_dict['out']['output_cprst_path']:
328 self.cmd.append('-cprestrt')
329 self.cmd.append(self.io_dict['out']['output_cprst_path'])
331 # general mpi properties
332 if self.mpi_bin:
333 mpi_cmd = [self.mpi_bin]
334 if self.mpi_np:
335 mpi_cmd.append('-n')
336 mpi_cmd.append(str(self.mpi_np))
337 if self.mpi_flags:
338 mpi_cmd.extend(self.mpi_flags)
339 self.cmd = mpi_cmd + self.cmd
341 # Run Biobb block
342 self.run_biobb()
344 # Copy files to host
345 self.copy_to_host()
347 # remove temporary folder(s)
348 self.tmp_files.extend([self.tmp_folder, "mdinfo"])
349 self.remove_tmp_files()
351 self.check_arguments(output_files_created=True, raise_exception=False)
353 return self.return_code
356def pmemd_mdrun(input_top_path: str, input_crd_path: str,
357 output_log_path: str, output_traj_path: str, output_rst_path: str,
358 input_mdin_path: Optional[str] = None, input_cpin_path: Optional[str] = None,
359 output_cpout_path: Optional[str] = None, output_cprst_path: Optional[str] = None,
360 output_mdinfo_path: Optional[str] = None, input_ref_path: Optional[str] = None,
361 properties: Optional[dict] = None, **kwargs) -> int:
362 """Create :class:`PmemdMDRun <pmemd.pmemd_mdrun.PmemdMDRun>`pmemd.pmemd_mdrun.PmemdMDRun class and
363 execute :meth:`launch() <pmemd.pmemd_mdrun.PmemdMDRun.launch>` method"""
364 return PmemdMDRun(**dict(locals())).launch()
367pmemd_mdrun.__doc__ = PmemdMDRun.__doc__
369main = PmemdMDRun.get_main(pmemd_mdrun, "Running molecular dynamics using pmemd tool from the AMBER MD package.")
371if __name__ == '__main__':
372 main()