1 12
import abc
2 12
import logging
3 12
import os
4 12
import subprocess as sp
5 12
from collections import OrderedDict
6 12
from enum import Enum
7

8 12
from .simulation import Simulation
9

10 12
logger = logging.getLogger(__name__)
11

12

13 12
class AMBER(Simulation, abc.ABC):
14
    """
15
    A wrapper for setting parameters and running MD with AMBER.
16
    """
17

18 12
    class Thermostat(Enum):
19
        """
20
        An enumeration of the different thermostat implemented in AMBER (option for ``ntt``).
21
        """
22

23 12
        Off = 0
24 12
        Berendsen = 1
25 12
        Andersen = 2
26 12
        Langevin = 3
27 12
        OptimizedIsoNoseHoover = 9
28 12
        StochasticIsoNoseHoover = 10
29 12
        Bussi = 11
30

31 12
    class Barostat(Enum):
32
        """
33
        An enumeration of the different barostat implemented in AMBER (option for ``barostat``).
34
        """
35

36 12
        Off = 0
37 12
        Berendsen = 1
38 12
        MonteCarlo = 2
39

40 12
    class GBModel(Enum):
41
        """
42
        An enumeration of the different Generalized Born Implicit Solvent model implemented in AMBER (option for
43
        ``igb``).
44
        """
45

46 12
        Off = 0
47 12
        HCT = 1
48 12
        OBC1 = 2
49 12
        OBC2 = 5
50 12
        GBn = 7
51 12
        GBn2 = 8
52 12
        vacuum = 6
53

54 12
    class BoxScaling(Enum):
55
        """
56
        An enumeration of the different PBC scaling options when running constant pressure simulations in AMBER (
57
        option for ``ntp``).
58
        """
59

60 12
        Off = 0
61 12
        Isotropic = 1
62 12
        Anisotropic = 2
63 12
        Semiisotropic = 3
64

65 12
    class Periodicity(Enum):
66
        """
67
        An enumeration of the different periodicity options in AMBER (option for ``ntb``).
68
        """
69

70 12
        Off = 0
71 12
        ConstantVolume = 1
72 12
        ConstantPressure = 2
73

74 12
    class Constraints(Enum):
75
        """
76
        An enumeration of the different bond constraint options in AMBER (option for ``ntc``).
77
        """
78

79 12
        Off = 1
80 12
        HBonds = 2
81 12
        AllBonds = 3
82

83 12
    class ForceEvaluation(Enum):
84
        """
85
        An enumeration of the different force evaluation options in AMBER (option for ``ntf``).
86
        """
87

88 12
        All_On = 1
89 12
        All_Off = 8
90 12
        HBonds_Off = 2
91 12
        AllBonds_Off = 3
92 12
        AllBonds_HAngles_Off = 4
93 12
        AllBonds_AllAngles_Off = 5
94 12
        AllBonds_AllAngles_HDihedrals_Off = 6
95 12
        AllBonds_AllAngles_AllDihedrals_Off = 7
96

97 12
    @property
98 12
    def restraint_file(self) -> str:
99
        """os.PathLike: The file containing NMR-style restraints for AMBER.
100

101
        .. note ::
102
            When running AMBER simulations, you can only use either an AMBER NMR-style
103
            restraints or a Plumed-style restraints and not both. If both are specified,
104
            an ``Exception`` will be thrown.
105
        """
106 12
        return self._restraint_file
107

108 12
    @restraint_file.setter
109 12
    def restraint_file(self, value: str):
110 12
        self._restraint_file = value
111

112 12
    @property
113 12
    def cntrl(self) -> dict:
114
        """
115
        An ordered dictionary of simulation "control" namelist parameters. The main purpose of this class attribute is
116
        to make it easy to override certain simulation parameters, such as positional restraints on dummy atoms, and
117
        the inclusion of exclusion of the NMR-style APR restraints.
118

119
        As of AMBER20, these are described, in part, in chapter 20 on ``pmemd``.
120

121
        .. note ::
122
            I can't recall why we wanted an ``OrderedDict`` here.
123

124
        .. note ::
125
            **This is fragile** and this could be hardened by making these ``ENUM``s and doing much more type-checking.
126
            These must be valid AMBER keywords or else the simulation will crash. The default keywords that are set when
127
            this class is initialized are partially overridden by the specific "config" functions detailed below.
128

129
        The default dictionary keys and values are as follows:
130

131
            - ``imin``          : 0
132
            - ``ntx``           : 1
133
            - ``irest``         : 0
134
            - ``maxcyc``        : 0
135
            - ``ncyc``          : 0
136
            - ``dt``            : 0.002
137
            - ``nstlim``        : 5000
138
            - ``ntpr``          : 500
139
            - ``ntwe``          : 500
140
            - ``ntwr``          : 5000
141
            - ``ntwx``          : 500
142
            - ``ntxo``          : 1
143
            - ``ioutfm``        : 1
144
            - ``ntf``           : 2
145
            - ``ntc``           : 2
146
            - ``cut``           : 8
147
            - ``igb``           : 0
148
            - ``tempi``         : 298.15
149
            - ``tempo``         : 298.15
150
            - ``pres0``         : 1.01325
151
            - ``ntt``           : Thermostat.Langevin
152
            - ``gamma_ln``      : 1.0
153
            - ``ig``            : -1
154
            - ``ntp``           : BoxScaling.Isotropic
155
            - ``barostat``      : Barostat.MonteCarlo
156
            - ``ntr``           : ``None``
157
            - ``restraint_wt``  : ``None``
158
            - ``restraintmask`` : ``None``
159
            - ``nmropt``        : 1
160
            - ``pencut``        : -1
161
        """
162 12
        return self._cntrl
163

164 12
    @cntrl.setter
165 12
    def cntrl(self, value: dict):
166 0
        self._cntrl = value
167

168 12
    @property
169 12
    def ewald(self) -> dict:
170
        """dict: Additional Ewald summation settings."""
171 12
        return self._ewald
172

173 12
    @ewald.setter
174 12
    def ewald(self, value: dict):
175 0
        self._ewald = value
176

177 12
    @property
178 12
    def wt(self) -> dict:
179
        """dict: "Weight" assigned to various simulation parameters. Written to the "&wt" line in the input file."""
180 12
        return self._wt
181

182 12
    @wt.setter
183 12
    def wt(self, value: dict):
184 0
        self._wt = value
185

186 12
    @property
187 12
    def group(self) -> dict:
188
        """dict: Group specification for restraints applied to multiple atoms.
189

190
        .. note::
191
            I don't recall ever having to this option and this seems distinct from applying restraints to a collection
192
            of atoms.
193
        """
194 12
        return self._group
195

196 12
    @group.setter
197
    def group(self, value):
198 0
        self._group = value
199

200 12
    @property
201 12
    def prefix(self) -> str:
202
        """str: The prefix for file names generated from this simulation."""
203 0
        return self._prefix
204

205 12
    @prefix.setter
206 12
    def prefix(self, new_prefix: str):
207 12
        self._prefix = new_prefix
208 12
        self.input = new_prefix + ".in"
209 12
        self.ref = new_prefix + ".inpcrd"
210 12
        self.output = new_prefix + ".out"
211 12
        self.restart = new_prefix + ".rst7"
212 12
        self.mdinfo = new_prefix + ".mdinfo"
213 12
        self.mdcrd = new_prefix + ".nc"
214 12
        self.mden = new_prefix + ".mden"
215

216 12
    @property
217
    def pmd(self):
218
        """dict: Option to apply harmonic restraints on collective variables (umbrella sampling) based on the **NFE**
219
        module of Amber. Users need to define the colvars file ``cv_file`` for this option to be written in the
220
        AMBER input file. The default values for ``output_file`` and ``output_freq`` is "pmd.txt" and 500,
221
        respectively."""
222 12
        return self._pmd
223

224 12
    @pmd.setter
225
    def pmd(self, value):
226 0
        self._pmd = value
227

228 12
    @property
229
    def smd(self):
230
        """dict: Option to run a steered molecular dynamics (SMD) using the **NFE** module of Amber. Users need to
231
        define the colvars file ``cv_file`` for this option to be written in the AMBER input file. The default values
232
        for ``output_file`` and ``output_freq`` is "smd.txt" and 500, respectively."""
233 12
        return self._smd
234

235 12
    @smd.setter
236
    def smd(self, value):
237 0
        self._smd = value
238

239 12
    def __init__(self):
240

241 12
        super().__init__()
242

243
        # I/O
244 12
        self._restraint_file = None
245

246
        # File names
247 12
        self.input = self._prefix + ".in"
248 12
        self.ref = self._prefix + ".inpcrd"
249 12
        self.output = self._prefix + ".out"
250 12
        self.restart = self._prefix + ".rst7"
251 12
        self.mdinfo = self._prefix + ".mdinfo"
252 12
        self.mdcrd = self._prefix + ".nc"
253 12
        self.mden = self._prefix + ".mden"
254

255
        # Input file cntrl settings (Default = NTP)
256 12
        self._cntrl = OrderedDict()
257 12
        self._cntrl["imin"] = 0
258 12
        self._cntrl["ntx"] = 1
259 12
        self._cntrl["irest"] = 0
260 12
        self._cntrl["maxcyc"] = 0
261 12
        self._cntrl["ncyc"] = 0
262 12
        self._cntrl["dt"] = 0.002
263 12
        self._cntrl["nstlim"] = 5000
264 12
        self._cntrl["ntpr"] = 500
265 12
        self._cntrl["ntwe"] = 500
266 12
        self._cntrl["ntwr"] = 5000
267 12
        self._cntrl["ntwx"] = 500
268 12
        self._cntrl["ntxo"] = 1
269 12
        self._cntrl["ioutfm"] = 1
270 12
        self._cntrl["ntf"] = self.ForceEvaluation.HBonds_Off.value
271 12
        self._cntrl["ntc"] = self.Constraints.HBonds.value
272 12
        self._cntrl["cut"] = 8.0
273 12
        self._cntrl["igb"] = self.GBModel.Off.value
274 12
        self._cntrl["tempi"] = self.temperature
275 12
        self._cntrl["temp0"] = self.temperature
276 12
        self._cntrl["pres0"] = self.pressure
277 12
        self._cntrl["ntt"] = self.Thermostat.Langevin.value
278 12
        self._cntrl["gamma_ln"] = 1.0
279 12
        self._cntrl["ig"] = -1
280 12
        self._cntrl["ntp"] = self.BoxScaling.Isotropic.value
281 12
        self._cntrl["barostat"] = self.Barostat.MonteCarlo.value
282 12
        self._cntrl["ntr"] = None
283 12
        self._cntrl["restraint_wt"] = None
284 12
        self._cntrl["restraintmask"] = None
285 12
        self._cntrl["nmropt"] = 1
286 12
        self._cntrl["pencut"] = -1
287

288
        # Other input file sections
289 12
        self._ewald = None
290 12
        self._wt = None  # or []
291 12
        self._group = None  # or []
292

293
        # NFE module(s)
294 12
        self._pmd = {
295
            "cv_file": None,
296
            "output_file": "pmd.txt",
297
            "output_freq": 500,
298
        }
299 12
        self._smd = {
300
            "cv_file": None,
301
            "output_file": "smd.txt",
302
            "output_freq": 500,
303
        }
304

305 12
    def _config_min(self):
306
        """
307
        Configure input settings for minimization (without periodic boundary conditions).
308
        """
309 12
        self.cntrl["imin"] = 1
310 12
        self.cntrl["ntx"] = 1
311 12
        self.cntrl["irest"] = 0
312 12
        self.cntrl["maxcyc"] = 5000
313 12
        self.cntrl["ncyc"] = 1000
314 12
        self.cntrl["dt"] = 0.0
315 12
        self.cntrl["nstlim"] = 0
316 12
        self.cntrl["ntpr"] = 100
317 12
        self.cntrl["ntwr"] = 5000
318 12
        self.cntrl["ntwx"] = 0
319 12
        self.cntrl["ntwe"] = 0
320 12
        self.cntrl["ntxo"] = 1
321 12
        self.cntrl["ntf"] = self.ForceEvaluation.All_On.value
322 12
        self.cntrl["ntc"] = self.Constraints.Off.value
323 12
        self.cntrl["ntt"] = self.Thermostat.Off.value
324 12
        self.cntrl["gamma_ln"] = 0.0
325 12
        self.cntrl["ig"] = 0
326 12
        self.cntrl["ntp"] = self.BoxScaling.Off.value
327 12
        self.cntrl["barostat"] = self.Barostat.Off.value
328 12
        self.mdcrd = None
329 12
        self.mden = None
330

331 12
    def _config_md(self, thermostat):
332
        """
333
        Configure input settings for MD.
334
        """
335 12
        self.cntrl["imin"] = 0
336 12
        self.cntrl["ntx"] = 1
337 12
        self.cntrl["irest"] = 0
338 12
        self.cntrl["maxcyc"] = 0
339 12
        self.cntrl["ncyc"] = 0
340 12
        self.cntrl["dt"] = 0.002
341 12
        self.cntrl["nstlim"] = 5000
342 12
        self.cntrl["ntpr"] = 500
343 12
        self.cntrl["ntwe"] = 500
344 12
        self.cntrl["ntwr"] = 5000
345 12
        self.cntrl["ntwx"] = 500
346 12
        self.cntrl["ntxo"] = 1
347 12
        self.cntrl["ioutfm"] = 1
348 12
        self.cntrl["ntf"] = self.ForceEvaluation.HBonds_Off.value
349 12
        self.cntrl["ntc"] = self.Constraints.HBonds.value
350 12
        self.cntrl["ntt"] = thermostat.value
351 12
        self.cntrl["gamma_ln"] = 1.0
352 12
        self.cntrl["ig"] = -1
353 12
        self.cntrl["cut"] = 9.0
354

355 12
    def config_vac_min(self):
356
        """
357
        Configure a reasonable input setting for an energy minimization run in vacuum. `Users can override the
358
        parameters set by this method.`
359
        """
360 0
        self._config_min()
361 0
        self.title = "Vacuum Minimization"
362 0
        self.cntrl["cut"] = 999.0
363 0
        self.cntrl["igb"] = self.GBModel.vacuum.value
364

365 12
    def config_vac_md(self, thermostat=Thermostat.Langevin):
366
        """
367
        Configure a reasonable input settings for MD in vacuum. `Users can override the parameters set by this method.`
368

369
        Parameters
370
        ----------
371
        thermostat: :class:`AMBER.Thermostat`, default=Thermostat.Langevin
372
            Option to choose one of six thermostats implemented in AMBER, highlighted values in parenthesis are the
373
            options set in the input file. **(1)** `Off` (``0``), **(2)** `Berendsen` (``1``), **(3)** `Andersen`
374
            (``2``), **(4)** `Langevin` (``3``), **(5)** `OptimizedIsoNoseHoover` (``9``), **(6)**
375
            `StochasticIsoNoseHoover` (``10``), and **(7)** `Bussi` (``11``).
376
        """
377

378 0
        self._config_md(thermostat)
379 0
        self.title = "Vacuum MD Simulation"
380 0
        self.cntrl["cut"] = 999.0
381 0
        self.cntrl["igb"] = self.GBModel.vacuum.value
382 0
        self.cntrl["ntp"] = self.BoxScaling.Off.value
383 0
        self.cntrl["ntb"] = self.Periodicity.Off.value
384 0
        self.cntrl["barostat"] = self.Barostat.Off.value
385

386 12
    def config_gb_min(self, gb_model=GBModel.HCT):
387
        """
388
        Configure a reasonable input setting for an energy minimization run with implicit solvent. `Users can
389
        override the parameters set by this method.`
390

391
        Parameters
392
        ----------
393
        gb_model: :class:`AMBER.GBModel`, default=GBModel.HCT
394
            Option to choose different implicit solvent model.
395
        """
396

397 12
        self._config_min()
398 12
        self.title = "GB Minimization"
399 12
        self.cntrl["cut"] = 999.0
400 12
        self.cntrl["igb"] = gb_model.value
401

402 12
    def config_gb_md(self, gb_model=GBModel.HCT, thermostat=Thermostat.Langevin):
403
        """
404
        Configure a reasonable input settings for MD with implicit solvent. `Users can override the parameters
405
        set by this method.`
406

407
        Parameters
408
        ----------
409
        gb_model: :class:`AMBER.GBModel`, default=GBModel.HCT
410
            Option to choose different implicit solvent model.
411
        thermostat: :class:`AMBER.Thermostat`, default=Thermostat.Langevin
412
            Option to choose one of six thermostats implemented in AMBER, highlighted values in parenthesis are the
413
            options set in the input file. **(1)** `Off` (``0``), **(2)** `Berendsen` (``1``), **(3)** `Andersen`
414
            (``2``), **(4)** `Langevin` (``3``), **(5)** `OptimizedIsoNoseHoover` (``9``), **(6)**
415
            `StochasticIsoNoseHoover` (``10``), and **(7)** `Bussi` (``11``).
416
        """
417

418 12
        self._config_md(thermostat)
419 12
        self.title = "GB MD Simulation"
420 12
        self.cntrl["cut"] = 999.0
421 12
        self.cntrl["igb"] = gb_model.value
422 12
        self.cntrl["ntp"] = self.BoxScaling.Off.value
423 12
        self.cntrl["ntb"] = self.Periodicity.Off.value
424 12
        self.cntrl["barostat"] = self.Barostat.Off.value
425

426 12
    def config_pbc_min(self):
427
        """
428
        Configure a reasonable input setting for an energy minimization run with periodic boundary conditions. `Users
429
        can override the parameters set by this method.`
430
        """
431 0
        self._config_min()
432 0
        self.title = "PBC Minimization"
433 0
        self.cntrl["igb"] = self.GBModel.Off.value
434

435 12
    def config_pbc_md(
436
        self,
437
        ensemble=Simulation.Ensemble.NPT,
438
        thermostat=Thermostat.Langevin,
439
        barostat=Barostat.MonteCarlo,
440
    ):
441
        """
442
        Configure a reasonable input setting for a MD run with periodic boundary conditions. `Users can override the
443
        parameters set by this method.`
444

445
        Parameters
446
        ----------
447
        ensemble: :class:`Simulation.Ensemble`, default=Ensemble.NPT
448
            Configure a MD simulation with NVE, NVT or NPT thermodynamic ensemble.
449
        thermostat: :class:`AMBER.Thermostat`, default=Thermostat.Langevin
450
            Option to choose one of six thermostats implemented in AMBER, highlighted values in parenthesis are the
451
            options set in the input file. **(1)** `Off` (``0``), **(2)** `Berendsen` (``1``), **(3)** `Andersen`
452
            (``2``), **(4)** `Langevin` (``3``), **(5)** `OptimizedIsoNoseHoover` (``9``), **(6)**
453
            `StochasticIsoNoseHoover` (``10``), and **(7)** `Bussi` (``11``).
454
        barostat: :class:`AMBER.Barostat`, default=Barostat.MonteCarlo
455
            Option to choose one of two barostats implemented in AMBER,  highlighted values in parenthesis are the
456
            options set in the input file. **(1)** `Off` (``0``), **(2)** `Berendsen` (``1``), and **(3)**
457
            `Monte Carlo` (``2``).
458
        """
459 0
        self._config_md(thermostat)
460 0
        self.title = "PBC MD Simulation"
461

462 0
        self.cntrl["igb"] = self.GBModel.Off.value
463 0
        self.cntrl["iwrap"] = 1
464

465 0
        if ensemble == self.Ensemble.NVE:
466 0
            self.cntrl["ntt"] = self.Thermostat.Off.value
467 0
            self.cntrl["ntb"] = self.Periodicity.ConstantVolume.value
468 0
            self.cntrl["barostat"] = self.Barostat.Off.value
469

470 0
        elif ensemble == self.Ensemble.NVT:
471 0
            self.cntrl["ntt"] = thermostat.value
472 0
            self.cntrl["ntb"] = self.Periodicity.ConstantVolume.value
473 0
            self.cntrl["barostat"] = self.Barostat.Off.value
474

475 0
        elif ensemble == self.Ensemble.NPT:
476 0
            self.cntrl["ntt"] = thermostat.value
477 0
            self.cntrl["pres0"] = self.pressure
478 0
            self.cntrl["ntp"] = self.BoxScaling.Isotropic.value
479 0
            self.cntrl["ntb"] = self.Periodicity.ConstantPressure.value
480 0
            self.cntrl["barostat"] = barostat.value
481

482 12
    def _write_dict_to_mdin(self, f, dictionary):
483
        """
484
        Write dictionary to file, following AMBER format.
485

486
        Parameters
487
        ----------
488
        f : TextIO
489
            File where the dictionary should be written
490
        dictionary : dict
491
            Dictionary of values
492

493
        """
494

495 12
        for key, val in dictionary.items():
496 12
            if val is not None:
497 12
                f.write("  {:15s} {:s},\n".format(key + " =", str(val)))
498

499
        # Write PLUMED option if preferred over Amber NMR restraints
500 12
        if self.plumed_file:
501 0
            f.write("  {:15s} {:s},\n".format("plumed = ", str(1)))
502 0
            f.write("  {:15s} {:s},\n".format("plumedfile =", f"'{self.plumed_file}'"))
503

504 12
        f.write(" /\n")
505

506 12
    def _write_input_file(self):
507
        """
508
        Write the input file specification to file.
509
        """
510 12
        logger.debug("Writing {}".format(self.input))
511 12
        with open(os.path.join(self.path, self.input), "w") as mdin:
512 12
            mdin.write("{}\n".format(self.title))
513 12
            mdin.write(" &cntrl\n")
514 12
            if self.pmd["cv_file"] is not None or self.smd["cv_file"] is not None:
515 0
                self.cntrl["infe"] = 1
516 12
            self._write_dict_to_mdin(mdin, self.cntrl)
517

518 12
            if self.ewald is not None:
519 0
                mdin.write(" &ewald\n")
520 0
                self._write_dict_to_mdin(mdin, self.ewald)
521

522 12
            if self.cntrl["nmropt"] == 1:
523 12
                if self.wt is not None:
524 0
                    for line in self.wt:
525 0
                        mdin.write(" " + line + "\n")
526 12
                mdin.write(" &wt type = 'END', /\n")
527

528
                # Specify Amber NMR file
529 12
                if self.restraint_file is not None:
530 0
                    mdin.write("DISANG = {}\n".format(self.restraint_file))
531 0
                    mdin.write("LISTOUT = POUT\n\n")
532

533 12
            if self.group is not None:
534 0
                mdin.write("{:s}".format(self.group))
535

536 12
            if self.pmd["cv_file"] is not None:
537 0
                mdin.write("&pmd \n")
538 0
                mdin.write("  cv_file = '{}',\n".format(self.pmd["cv_file"]))
539 0
                mdin.write("  output_file = '{}',\n".format(self.pmd["output_file"]))
540 0
                mdin.write("  output_freq = {},\n".format(self.pmd["output_freq"]))
541 0
                mdin.write("/\n")
542

543 12
            if self.smd["cv_file"] is not None:
544 0
                mdin.write("&smd \n")
545 0
                mdin.write("  cv_file = '{}',\n".format(self.smd["cv_file"]))
546 0
                mdin.write("  output_file = '{}',\n".format(self.smd["output_file"]))
547 0
                mdin.write("  output_freq = {},\n".format(self.smd["output_freq"]))
548 0
                mdin.write("/\n")
549

550 12
    def run(self, soft_minimize=False, overwrite=False, fail_ok=False):
551
        """
552
        Method to run Molecular Dynamics simulation with AMBER.
553

554
        Parameters
555
        ----------
556
        soft_minimize: bool, optional, default=False
557
            Whether to slowly turn on non-bonded interactions so that restraints get enforced first.
558
        overwrite: bool, optional, default=False
559
            Whether to overwrite simulation files.
560
        fail_ok: bool, optional, default=False
561
            Whether a failing simulation should stop execution of ``pAPRika``.
562

563
        """
564

565 12
        if overwrite or not self.check_complete():
566

567
            # These settings hardcoded at the moment ... possibly expose for
568
            # editing in the future
569 12
            if soft_minimize:
570
                # Set a burn in value that is 25% of the way between ncyc and
571
                # maxcyc
572 0
                ncyc = self.cntrl["ncyc"]
573 0
                maxcyc = self.cntrl["maxcyc"]
574 0
                burn_in = int(float(ncyc) + 0.20 * (float(maxcyc) - float(ncyc)))
575
                # If the burn_in value is nuts, then just set it to zero
576 0
                if burn_in < 0 or burn_in >= maxcyc:
577 0
                    burn_in = 0
578
                # Set an end_soft value that is 75% of way between ncyc and
579
                # maxcyc
580 0
                end_soft = int(float(ncyc) + 0.60 * (float(maxcyc) - float(ncyc)))
581 0
                self.wt = [
582
                    "&wt type = 'NB', istep1=0, istep2={:.0f}, value1 = 0.0, value2=0.0, IINC=50, /".format(
583
                        burn_in
584
                    ),
585
                    "&wt type = 'NB', istep1={:.0f}, istep2={:.0f}, value1 = 0.0, value2=1.0, IINC=50, /".format(
586
                        burn_in, end_soft
587
                    ),
588
                ]
589

590
            # Check restraints file
591 12
            if self.restraint_file and self.plumed_file:
592 0
                raise Exception(
593
                    "Cannot use both NMR-style and Plumed-style restraints at the same time."
594
                )
595

596
            # Add CUDA_VISIBLE_DEVICES variable to env dict
597 12
            if self.gpu_devices is not None:
598 0
                os.environ = dict(
599
                    os.environ, CUDA_VISIBLE_DEVICES=str(self.gpu_devices)
600
                )
601

602
            # Set Plumed kernel library to env dict
603 12
            if self.plumed_file is not None:
604 0
                self._set_plumed_kernel()
605

606
            # _amber_write_input_file(self.path+'/'+self.input, self.min, title='GB Minimization.')
607 12
            self._write_input_file()
608

609 12
            if self.cntrl["imin"] == 1:
610 12
                logger.info("Running Minimization at {}".format(self.path))
611
            else:
612 12
                logger.info("Running MD at {}".format(self.path))
613

614
            # Create executable list for subprocess
615 12
            exec_list = self.executable.split() + ["-O", "-p", self.topology]
616 12
            if self.ref is not None:
617 12
                exec_list += ["-ref", self.ref]
618 12
            exec_list += [
619
                "-c",
620
                self.coordinates,
621
                "-i",
622
                self.input,
623
                "-o",
624
                self.output,
625
                "-r",
626
                self.restart,
627
            ]
628 12
            if self.mdcrd is not None:
629 12
                exec_list += ["-x", self.mdcrd]
630 12
            if self.mdinfo is not None:
631 12
                exec_list += ["-inf", self.mdinfo]
632 12
            if self.mden is not None:
633 12
                exec_list += ["-e", self.mden]
634

635 12
            logger.debug("Exec line: " + " ".join(exec_list))
636

637
            # Execute
638 12
            amber_output = sp.Popen(
639
                exec_list,
640
                cwd=self.path,
641
                stdout=sp.PIPE,
642
                stderr=sp.PIPE,
643
                env=os.environ,
644
            )
645

646 12
            amber_stdout = amber_output.stdout.read().splitlines()
647 12
            amber_stderr = amber_output.stderr.read().splitlines()
648

649
            # Report any stdout/stderr which are output from execution
650 12
            if amber_stdout:
651 0
                logger.info("STDOUT received from AMBER execution")
652 0
                for line in amber_stdout:
653 0
                    logger.info(line)
654

655 12
            if amber_stderr:
656 0
                logger.info("STDERR received from AMBER execution")
657 0
                for line in amber_stderr:
658 0
                    logger.info(line)
659

660
            # Check completion status
661 12
            if self.cntrl["imin"] == 1 and self.check_complete():
662 12
                logger.info("Minimization completed...")
663 12
            elif self.check_complete():
664 12
                logger.info("MD completed ...")
665
            else:
666 0
                logger.info(
667
                    "Simulation did not complete when executing the following ...."
668
                )
669 0
                logger.info(" ".join(exec_list))
670 0
                if not fail_ok:
671 0
                    raise Exception(
672
                        "Exiting due to failed simulation! Check logging info."
673
                    )
674

675
        else:
676 0
            logger.info(
677
                "Completed output detected ... Skipping. Use: run(overwrite=True) to overwrite"
678
            )
679

680 12
    def check_complete(self, alternate_file=None):
681
        """
682
        Check for the string "TIMINGS" in ``self.output`` file. If "TIMINGS" is found, then the simulation completed
683
        successfully, as of AMBER20.
684

685
        Parameters
686
        ----------
687
        alternate_file : os.PathLike, optional, default=None
688
            If present, check for "TIMINGS" in this file rather than ``self.output``.
689

690
        Returns
691
        -------
692
        timings : bool
693
            True if "TIMINGS" is found in file. False, otherwise.
694

695
        """
696

697
        # Assume not completed
698 12
        complete = False
699

700 12
        if alternate_file:
701 0
            output_file = alternate_file
702
        else:
703 12
            output_file = os.path.join(self.path, self.output)
704

705 12
        if os.path.isfile(output_file):
706 12
            with open(output_file, "r") as f:
707 12
                strings = f.read()
708 12
                if " TIMINGS" in strings:
709 12
                    complete = True
710 12
        if complete:
711 12
            logger.debug("{} has TIMINGS".format(output_file))
712
        else:
713 12
            logger.debug("{} does not have TIMINGS".format(output_file))
714

715 12
        return complete

Read our documentation on viewing source code .

Loading