Source code for openmmtools.forcefactories

#!/usr/bin/env python

# =============================================================================
# MODULE DOCSTRING
# =============================================================================

"""
Factories to manipulate OpenMM System forces.

"""


# =============================================================================
# GLOBAL IMPORTS
# =============================================================================

import copy

import numpy as np
import mdtraj
try:
    import openmm
    from openmm import unit
except ImportError:  # OpenMM < 7.6
    from simtk import openmm, unit

from openmmtools import forces


# =============================================================================
# FACTORY FUNCTIONS
# =============================================================================

[docs] def replace_reaction_field(reference_system, switch_width=1.0*unit.angstrom, return_copy=True, shifted=False): """Return a system converted to use a switched reaction-field electrostatics using :class:`openmmtools.forces.UnshiftedReactionField`. This will add an `UnshiftedReactionFieldForce` or `SwitchedReactionFieldForce` for each `NonbondedForce` that utilizes `CutoffPeriodic`. Note that `AbsoluteAlchemicalFactory.create_alchemical_system()` can NOT handle the resulting `System` object yet since the `CustomNonbondedForce` are not recognized and re-coded. Parameters ---------- reference_system : openmm.System The system to use as a reference. This will be modified only if `return_copy` is `False`. switch_width : openmm.unit.Quantity, default=1.0*angstrom Switch width for electrostatics (units of distance). return_copy : bool, optional, default=True If `True`, `reference_system` is not modified, and a copy is returned. Setting it to `False` speeds up the function execution but modifies the `reference_system` object. shifted : bool, optional, default=False If `True`, a shifted reaction-field will be used. Returns ------- system : openmm.System System with reaction-field converted to c_rf = 0 """ if return_copy: system = copy.deepcopy(reference_system) else: system = reference_system if shifted: force_constructor = getattr(forces, 'SwitchedReactionFieldForce') else: force_constructor = getattr(forces, 'UnshiftedReactionFieldForce') # Add an reaction field for each CutoffPeriodic NonbondedForce. for reference_force in forces.find_forces(system, openmm.NonbondedForce).values(): if reference_force.getNonbondedMethod() == openmm.NonbondedForce.CutoffPeriodic: reaction_field_force = force_constructor.from_nonbonded_force(reference_force, switch_width=switch_width) system.addForce(reaction_field_force) # Remove particle electrostatics from reference force, but leave exceptions. for particle_index in range(reference_force.getNumParticles()): charge, sigma, epsilon = reference_force.getParticleParameters(particle_index) reference_force.setParticleParameters(particle_index, abs(0.0*charge), sigma, epsilon) return system
# ============================================================================= # RESTRAIN ATOMS # ============================================================================= def restrain_atoms_by_dsl(thermodynamic_state, sampler_state, topology, atoms_dsl, **kwargs): # Make sure the topology is an MDTraj topology. if isinstance(topology, mdtraj.Topology): mdtraj_topology = topology else: mdtraj_topology = mdtraj.Topology.from_openmm(topology) # Determine indices of the atoms to restrain. restrained_atoms = mdtraj_topology.select(atoms_dsl).tolist() restrain_atoms(thermodynamic_state, sampler_state, restrained_atoms, **kwargs)
[docs] def restrain_atoms(thermodynamic_state, sampler_state, restrained_atoms, sigma=3.0*unit.angstroms): """Apply a soft harmonic restraint to the given atoms. This modifies the ``ThermodynamicState`` object. Parameters ---------- thermodynamic_state : openmmtools.states.ThermodynamicState The thermodynamic state with the system. This will be modified. sampler_state : openmmtools.states.SamplerState The sampler state with the positions. topology : mdtraj.Topology or openmm.Topology The topology of the system. atoms_dsl : str The MDTraj DSL string for selecting the atoms to restrain. sigma : openmm.unit.Quantity, optional Controls the strength of the restrain. The smaller, the tighter (units of distance, default is 3.0*angstrom). """ K = thermodynamic_state.kT / sigma**2 # Spring constant. system = thermodynamic_state.system # This is a copy. # Check that there are atoms to restrain. if len(restrained_atoms) == 0: raise ValueError('No atoms to restrain.') # We need to translate the restrained molecule to the origin # to avoid MonteCarloBarostat rejections (see openmm#1854). if thermodynamic_state.pressure is not None: # First, determine all the molecule atoms. Reference platform is the cheapest to allocate? reference_platform = openmm.Platform.getPlatformByName('Reference') integrator = openmm.VerletIntegrator(1.0*unit.femtosecond) context = openmm.Context(system, integrator, reference_platform) molecules_atoms = context.getMolecules() del context, integrator # Make sure the atoms to restrain belong only to a single molecule. molecules_atoms = [set(molecule_atoms) for molecule_atoms in molecules_atoms] restrained_atoms_set = set(restrained_atoms) restrained_molecule_atoms = None for molecule_atoms in molecules_atoms: if restrained_atoms_set.issubset(molecule_atoms): # Convert set to list to use it as numpy array indices. restrained_molecule_atoms = list(molecule_atoms) break if restrained_molecule_atoms is None: raise ValueError('Cannot match the restrained atoms to any molecule. Restraining ' 'two molecules is not supported when using a MonteCarloBarostat.') # Translate system so that the center of geometry is in # the origin to reduce the barostat rejections. distance_unit = sampler_state.positions.unit centroid = np.mean(sampler_state.positions[restrained_molecule_atoms,:] / distance_unit, axis=0) sampler_state.positions -= centroid * distance_unit # Create a CustomExternalForce to restrain all atoms. if thermodynamic_state.is_periodic: energy_expression = '(K/2)*periodicdistance(x, y, z, x0, y0, z0)^2' # periodic distance else: energy_expression = '(K/2)*((x-x0)^2 + (y-y0)^2 + (z-z0)^2)' # non-periodic distance restraint_force = openmm.CustomExternalForce(energy_expression) # Adding the spring constant as a global parameter allows us to turn it off if desired restraint_force.addGlobalParameter('K', K) restraint_force.addPerParticleParameter('x0') restraint_force.addPerParticleParameter('y0') restraint_force.addPerParticleParameter('z0') for index in restrained_atoms: parameters = sampler_state.positions[index,:].value_in_unit_system(unit.md_unit_system) restraint_force.addParticle(index, parameters) # Update thermodynamic state. system.addForce(restraint_force) thermodynamic_state.system = system
if __name__ == '__main__': import doctest doctest.testmod()