Skip to content

Commit

Permalink
Bugfixes for OpenMM remote runs
Browse files Browse the repository at this point in the history
  • Loading branch information
avirshup committed Jun 28, 2017
1 parent 911ac49 commit 641aaa7
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 53 deletions.
28 changes: 22 additions & 6 deletions moldesign/_tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,25 +144,41 @@ def assert_something_resembling_minimization_happened(p0, e0, traj, mol):


def assert_something_resembling_dynamics_happened(traj, mol, p0, t0, duration):
""" Checks that dynamics routines have fulfilled their obligations.
""" Checks that dynamics routines have produced a consistent trajectory
"""
frame_interval = mol.integrator.frame_interval
if isinstance(frame_interval, int):
frame_interval *= mol.integrator.timestep

assert mol is traj.mol
assert traj.time[0] == t0
assert mol.time == traj.time[-1]
assert_almost_equal(traj.positions[-1], mol.positions)

# Energy test is not exact due to some non-deterministic kernels (e.g., OpenMM CPU model)
assert abs(traj.potential_energy[-1] - mol.calculate_potential_energy()) < 1e-10 * u.eV
assert_almost_equal(traj.positions[-1], mol.positions)
# Note: this is WAY too loose right now ...
assert abs(traj.potential_energy[-1] - mol.calculate_potential_energy()) < 1e-5 * u.eV

# lower precision for first step due to noise from single-precision positions (i.e. OpenMM CPU)
assert_almost_equal(traj.positions[0], p0, decimal=5)

lasttime = -np.inf * u.fs
for step in traj:
expected_end = t0 + duration
for istep, step in enumerate(traj):
assert istep < len(traj) # I certainly hope so!
assert step.time > lasttime

# currently we permit the integrator to be a little off in how long it integrates for
assert mol.time - traj.time[0] > (duration - mol.integrator.params.frame_interval)
if istep == len(traj) - 1:
# last frame - it just needs
assert step.time >= expected_end - 1e-5 * u.timestep
break

elif istep != 0:
assert (step.time - lasttime - frame_interval) < 1e-5 * mol.integrator.timestep
lasttime = step.time

# If frame_interval doesn't divide duration exactly, it's permitted to go beyond duration
assert duration <= mol.time - traj.time[0] < duration + frame_interval

if mol.constraints or mol.energy_model.params.get('constrain_hbonds', False):
assert_constraints_satisfied(traj, p0, mol)
Expand Down
39 changes: 24 additions & 15 deletions moldesign/_tests/molecule_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import moldesign.units as u
from .helpers import get_data_path

from moldesign.interfaces.openbabel import force_remote as openbabel_missing


molecule_standards = {}

Expand Down Expand Up @@ -61,13 +63,18 @@ def random_atoms_from_3aid(pdb3aid):


@pytest.fixture(scope='session')
def small_molecule():
def create_small_molecule():
mol = mdt.from_smiles('CNCOS(=O)C')
mol.positions += 0.001*np.random.random(mol.positions.shape)*u.angstrom # move out of minimum
return mol


@pytest.fixture
def small_molecule(create_small_molecule):
return create_small_molecule.copy()


@pytest.fixture()
def benzene():
return mdt.from_smiles('c1ccccc1')

Expand Down Expand Up @@ -186,8 +193,8 @@ def nucleic():
# Molecules with forcefields assigned - these use a session-scoped constructor w/ a copy factory

@pytest.fixture(scope='session')
def create_parameterize_zeros(small_molecule):
return _param_small_mol(small_molecule, 'zero')
def create_parameterize_zeros(create_small_molecule):
return _param_small_mol(create_small_molecule.copy(), 'zero')


@typedfixture('hasmodel')
Expand All @@ -196,11 +203,11 @@ def parameterize_zeros(create_parameterize_zeros):


@pytest.fixture(scope='session')
def create_parameterize_am1bcc(small_molecule):
def create_parameterize_am1bcc(create_small_molecule):
""" We don't use this fixture directly, rather use another fixture that copies these results
so that we don't have to repeatedly call tleap/antechamber
"""
return _param_small_mol(small_molecule, 'am1-bcc')
return _param_small_mol(create_small_molecule.copy(), 'am1-bcc')


@typedfixture('hasmodel')
Expand All @@ -209,25 +216,27 @@ def parameterize_am1bcc(create_parameterize_am1bcc):


@pytest.fixture(scope='session')
def create_gaff_model_gasteiger(small_molecule):
def create_gaff_model_gasteiger(create_small_molecule):
""" We don't use this fixture directly, rather use another fixture that copies these results
so that we don't have to repeatedly call tleap/antechamber
"""
small_molecule.set_energy_model(mdt.models.GaffSmallMolecule, partial_charges='gasteiger')
small_molecule.energy_model.prep()
return small_molecule
mol = create_small_molecule.copy()
mol.set_energy_model(mdt.models.GaffSmallMolecule, partial_charges='gasteiger')
mol.energy_model.prep()
return mol


@typedfixture('hasmodel')
def gaff_model_gasteiger(create_gaff_model_gasteiger):
return create_gaff_model_gasteiger.copy()


def _param_small_mol(small_molecule, chargemodel):
params = mdt.create_ff_parameters(small_molecule, charges=chargemodel, baseff='gaff2')
params.assign(small_molecule)
small_molecule.set_energy_model(mdt.models.ForceField)
return small_molecule
def _param_small_mol(create_small_molecule, chargemodel):
mol = create_small_molecule.copy()
params = mdt.create_ff_parameters(mol, charges=chargemodel, baseff='gaff2')
params.assign(mol)
mol.set_energy_model(mdt.models.ForceField)
return mol


@pytest.fixture(scope='session')
Expand All @@ -244,4 +253,4 @@ def create_protein_default_amber_forcefield(pdb1yu8):

@typedfixture('hasmodel')
def protein_default_amber_forcefield(create_protein_default_amber_forcefield):
return create_protein_default_amber_forcefield.copy()
return create_protein_default_amber_forcefield.copy()
2 changes: 1 addition & 1 deletion moldesign/_tests/test_openbabel_xface.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import moldesign as mdt

from .molecule_fixtures import small_molecule
from .molecule_fixtures import *


registered_types = {}
Expand Down
46 changes: 29 additions & 17 deletions moldesign/_tests/test_openmm_xface.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import moldesign as mdt
from moldesign import units as u
from moldesign.interfaces.openmm import force_remote as missing_openmm

from . import helpers
from .molecule_fixtures import *
Expand Down Expand Up @@ -52,18 +53,21 @@ def protein_freeze_hbonds(protein):
return mol


INTEGRATOR_PARAMS = dict(timestep=1.0 * u.fs,
constrain_hbonds=False,
constrain_water=False)


@pytest.fixture
def langevin():
return mdt.integrators.OpenMMLangevin(temperature=300.0*u.kelvin,
constrain_hbonds=False,
constrain_water=False)
return mdt.integrators.OpenMMLangevin(frame_interval=500,
**INTEGRATOR_PARAMS)


@pytest.fixture
def verlet():
return mdt.integrators.OpenMMVerlet(timestep=1.0 * u.fs,
constrain_hbonds=False,
constrain_water=False)
return mdt.integrators.OpenMMVerlet(frame_interval=250*u.fs,
**INTEGRATOR_PARAMS)


@pytest.mark.parametrize('objkey', TESTSYTEMS)
Expand Down Expand Up @@ -113,16 +117,17 @@ def test_openmm_dynamics(systemkey, integratorkey, request):
mol.set_integrator(request.getfixturevalue(integratorkey))

mol.integrator.prep()
assert mol.integrator.sim.system is mol.energy_model.sim.system
if not missing_openmm:
assert mol.integrator.sim.system is mol.energy_model.sim.system

initially_satisfied_constraints = all(c.satisfied() for c in mol.constraints)

p0 = mol.positions.copy()
t0 = mol.time
traj = mol.run(10.0 * u.ps)
traj = mol.run(2.0 * u.ps)

# Basic dynamics and constraint sanity checks:
helpers.assert_something_resembling_dynamics_happened(traj, mol, p0, t0, 10.0*u.ps)
helpers.assert_something_resembling_dynamics_happened(traj, mol, p0, t0, 2.0*u.ps)

if 'temperature' in mol.integrator.params:
temp = mol.integrator.params.temperature
Expand All @@ -144,6 +149,8 @@ def test_openmm_dynamics(systemkey, integratorkey, request):
def test_cleared_constraints_are_no_longer_applied(protein_custom_constraints, langevin):
mol = protein_custom_constraints

langevin.frame_interval = 500

t0 = mol.time
p0 = mol.positions.copy()
mol.set_integrator(langevin)
Expand All @@ -162,12 +169,12 @@ def test_cleared_constraints_are_no_longer_applied(protein_custom_constraints, l

t1 = mol.time
p1 = mol.positions.copy()
traj = mol.run(10.0 * u.ps)
traj = mol.run(2.0 * u.ps)

for constraint in oldconstraints:
assert not constraint.satisfied() # it would be very very unlikely, I think

helpers.assert_something_resembling_dynamics_happened(traj, mol, p1, t1, 5*u.ps)
helpers.assert_something_resembling_dynamics_happened(traj, mol, p1, t1, 2*u.ps)


@pytest.mark.parametrize('integkey', INTEGRATORS)
Expand All @@ -176,24 +183,28 @@ def test_unsupported_constraint_types(protein, integkey, request):
integrator = request.getfixturevalue(integkey)
protein.set_integrator(integrator)

assert protein.energy_model._prepped
if not missing_openmm:
assert protein.energy_model._prepped

protein.constrain_dihedral(*protein.atoms[:4])
assert not protein.energy_model._prepped

if not missing_openmm:
assert not protein.energy_model._prepped

protein.calculate(use_cache=False) # this should work, because there's no motion invovled

with pytest.raises(mdt.exceptions.NotSupportedError):
traj = protein.run(1*u.ps)
traj = protein.run(1.0*u.ps)

t0 = protein.time
p0 = protein.positions.copy()
protein.clear_constraints()
traj = protein.run(1*u.ps) # should NOT raise a fuss now
helpers.assert_something_resembling_dynamics_happened(traj, protein, p0, t0, 1*u.ps)
traj = protein.run(1.0*u.ps) # should NOT raise a fuss now
helpers.assert_something_resembling_dynamics_happened(traj, protein, p0, t0, 1.0*u.ps)

protein.constrain_angle(*protein.atoms[:3])
with pytest.raises(mdt.exceptions.NotSupportedError):
protein.run(1*u.ps)
protein.run(1.0*u.ps)


@pytest.mark.parametrize('integkey', INTEGRATORS)
Expand All @@ -214,6 +225,7 @@ def test_fallback_to_builtin_minimizer_for_arbitrary_constraints(small_mol, inte
helpers.assert_something_resembling_minimization_happened(p0, e0, traj, mol)


@pytest.mark.skipif(missing_openmm, reason='OpenMM not installed')
def test_list_platforms(): # doesn't do much right now
platforms = mdt.interfaces.openmm.list_openmmplatforms()
print('Found platforms %d: ', (len(platforms), platforms))
32 changes: 18 additions & 14 deletions moldesign/min/scipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ def _run(self):
print('WARNING: no convergence criteria for this method; using defaults')

self._optimize_kwargs = dict(method=self._METHOD_NAME,
options=options,
constraints=self._make_constraints())
options=options)
self._constraint_multiplier = 1.0

result = scipy.optimize.minimize(self.objective,
self._coords_to_vector(self.mol.positions),
jac=grad,
callback=self.callback,
constraints=self._make_constraints(),
**self._optimize_kwargs)

if self.mol.constraints:
Expand All @@ -84,18 +85,6 @@ def _run(self):
self.mol.positions = finalprops.positions
self.mol.properties = finalprops

def _make_constraints(self):
from .. import geom

constraints = []
self._constraint_multiplier = 1.0
for constraint in geom.get_base_constraints(self.mol.constraints):
fun, jac = self._make_constraint_funs(constraint)
constraints.append(dict(type='eq',
fun=fun,
jac=jac))
return constraints

def _force_constraint_convergence(self, result):
""" Make sure that all constraints are satisfied, ramp up the constraint functions if not
Expand All @@ -111,14 +100,29 @@ def _force_constraint_convergence(self, result):
else:
return result

print('Constraints not satisfied; raising penalties ...')

self._constraint_multiplier *= 10.0
result = scipy.optimize.minimize(self.objective,
self._coords_to_vector(self.mol.positions),
jac=self.grad if self.gradtype=='analytical' else None,
callback=self.callback,
constraints=self._make_constraints(),
**self._optimize_kwargs)
return result

def _make_constraints(self):
from .. import geom

constraints = []
for constraint in geom.get_base_constraints(self.mol.constraints):
fun, jac = self._make_constraint_funs(constraint)
constraints.append(dict(type='eq',
fun=fun,
jac=jac))
return constraints


def _make_constraint_funs(self, const):
def fun(v):
self._sync_positions(v)
Expand Down

0 comments on commit 641aaa7

Please sign in to comment.