Skip to content

Commit

Permalink
adapt FEP tests to not trigger GROMACS exclusion/rlist limitation
Browse files Browse the repository at this point in the history
- add new kwarg exit_on_error=True|False to run runMD_or_exit() to only raise
  exceptions instead of calling sys.exit(); default is old behavior (True)
- added tests for runMD_or_exit() failure modes
- use better error handling in runMD_or_exit to check if we triggered the
  non-bonded interactions beyond cutoff issue and if so, pass with xfail
- close #175
  • Loading branch information
orbeckst committed Jun 30, 2023
1 parent 8993472 commit 62d7fa5
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Enhancements
* new workflows module (#217)
* new automated dihedral analysis workflow (detect dihedrals with SMARTS,
analyze with EnsembleAnalysis, and generate seaborn violinplots) (#217)
* add new exit_on_error=False|True argument to run.runMD_or_exit() so
that failures just raise exceptions and not call sys.exit() (PR #249)

Fixes

Expand Down
4 changes: 2 additions & 2 deletions mdpow/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def get_mdp_files(cfg, protocols):
logger.debug("%(protocol)s: Using MDP file %(mdp)r from config file", vars())
return mdpfiles

def runMD_or_exit(S, protocol, params, cfg, exit_on_error=False, **kwargs):
def runMD_or_exit(S, protocol, params, cfg, exit_on_error=True, **kwargs):
"""run simulation
Can launch :program:`mdrun` itself (:class:`gromacs.run.MDrunner`) or exit so
Expand All @@ -116,7 +116,7 @@ def runMD_or_exit(S, protocol, params, cfg, exit_on_error=False, **kwargs):
:class:`~gromacs.tools.Mdrun`.
.. versionchanged:: 0.9.0
New kwarg `exit_in_error`.
New kwarg `exit_on_error`.
"""
dirname = kwargs.pop("dirname", None)
Expand Down
23 changes: 18 additions & 5 deletions mdpow/tests/test_fep_script.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from . import tempdir as td

import os
import pytest
import pybol

from . import tempdir as td

import gromacs

import mdpow
Expand Down Expand Up @@ -43,14 +44,26 @@ def _run_fep(self, solvent, dirname):
gromacs.cbook.edit_mdp(mdp,
new_mdp=os.path.join(os.getcwd(), fep_mdp_name),
cutoff_scheme="group")
self.S = fep_simulation(cfg, solvent, dirname=dirname)
self.savefilename = fep_simulation(cfg, solvent, dirname=dirname, exit_on_error=False)

def test_default_run(self):
def test_default_run(self, capsys):
with gromacs.utilities.in_dir(self.tmpdir.name, create=False):
try:
self._run_fep('water', 'benzene/')
except Exception as err:
raise AssertionError('FEP simulations failed with exception:\n{0}'.format(str(err)))
# check if log file contains
# 'There are 2 perturbed non-bonded pair interactions beyond the pair-list cutoff'
# This error can happen and still counts as passing. (It is stochastic and is due
# to a poorly designed test system together with GROMACS having become better at detecting
# internal problems.)
captured = capsys.readouterr()
for line in captured:
if "perturbed non-bonded pair interactions beyond the pair-list cutoff'" in line:
pytest.xfail("Stochastic test failure (perturbed non-bonded beyond cutoff). "
"Still works as expected, see #175.")
break
else:
raise AssertionError('FEP simulations failed with exception:\n{0}'.format(str(err)))

assert os.path.exists(os.path.join(self.tmpdir.name,
'benzene', 'FEP', 'water', 'VDW', '0000', 'md.edr'))
86 changes: 86 additions & 0 deletions mdpow/tests/test_run.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import pytest
import pybol

import gromacs.run
import gromacs.exceptions

from numpy.testing import assert_equal

import mdpow.run
import mdpow.config
import mdpow.equil

from . import RESOURCES

@pytest.fixture
def cfg():
# default bundled config
return mdpow.config.get_configuration()


@pytest.mark.parametrize("protocols", [["energy_minimize"],
["MD_relaxed"],
["MD_NPT", "FEP"],
Expand All @@ -35,3 +43,81 @@ def test_get_mdp_files_ValueError(cfg):
cfg.conf['FEP']['mdp'] = "smoke_and_mirror.mdp"
with pytest.raises(ValueError):
mdpow.run.get_mdp_files(cfg, ["MD_NPT", "FEP"])


# To test the failure modes of runMD_or_exit() we mock the functions
# and methods that would return failures, so that we don't have to
# actually run simulations.

@pytest.fixture
def MDrunner_failure(monkeypatch):
# mock gromacs.run.MDrunner: pretend that the simulation failed
def mock_run_check(*args, **kwargs):
return False
monkeypatch.setattr(gromacs.run.MDrunner, 'run_check', mock_run_check)

# mock gromacs.run.check_mdrun_success(logfile)
@pytest.fixture
def check_mdrun_success_failure(monkeypatch):
# pretend simulation has not completed as indicated by log file
def mock_check_mdrun_success(arg):
return False
monkeypatch.setattr(gromacs.run, 'check_mdrun_success', mock_check_mdrun_success)

@pytest.fixture
def check_mdrun_success_none(monkeypatch):
# pretend no simulation has been run so there's no logfile to
# check and check_mdrun_success() returns None
def mock_check_mdrun_success(arg):
return None
monkeypatch.setattr(gromacs.run, 'check_mdrun_success', mock_check_mdrun_success)


@pytest.mark.parametrize("runlocal,exception",
[(True, gromacs.exceptions.GromacsError),
(False, gromacs.exceptions.MissingDataError)])
def test_runMD_or_exit_exceptions(runlocal, exception, cfg, MDrunner_failure, check_mdrun_success_failure,
monkeypatch, tmpdir):
params = {'deffnm': 'md'}
S = {}

def mock_getboolean(*args):
return runlocal
monkeypatch.setattr(cfg, "getboolean", mock_getboolean)

with pytest.raises(exception):
mdpow.run.runMD_or_exit(S, "FEP", params, cfg,
dirname=str(tmpdir),
exit_on_error=False)

def test_runMD_or_exit_None(cfg, check_mdrun_success_none, monkeypatch, tmpdir):
# special case where runlocal=False and no simulation has been run
# so there's no logfile to check and check_mdrun_success() returns
# None
params = {'deffnm': 'md'}
S = {}

def mock_getboolean(*args):
return False
monkeypatch.setattr(cfg, "getboolean", mock_getboolean)

return_value = mdpow.run.runMD_or_exit(S, "FEP", params, cfg,
dirname=str(tmpdir),
exit_on_error=False)
assert return_value is None


@pytest.mark.parametrize("runlocal", [True, False])
def test_runMD_or_exit_SysExit(runlocal, cfg, MDrunner_failure, check_mdrun_success_failure,
monkeypatch, tmpdir):
params = {'deffnm': 'md'}
S = {}

def mock_getboolean(*args):
return runlocal
monkeypatch.setattr(cfg, "getboolean", mock_getboolean)

with pytest.raises(SystemExit):
mdpow.run.runMD_or_exit(S, "FEP", params, cfg,
dirname=str(tmpdir),
exit_on_error=True)

0 comments on commit 62d7fa5

Please sign in to comment.